1.Sort Colors
[Sort Colors]https://leetcode.com/problems/sort-colors/description/
Solution:因为题目中已经限定了红白蓝,也就是说整个数组就是0,1,2组成了,因此直接的想法就是交换。还有hashmap的做法,这里不做赘述
class Solution {
public void sortColors(int[] nums) {
if (nums == null && nums.length <= 1) {
return;
}
int left = 0, right = nums.length - 1;
int i = 0;
while (i <= right) {
if (nums[i] == 0) {
swap(nums, left , i);
left++;
i++;
} else if (nums[i] == 1 ) {
i++;
} else {
swap(nums,i,right);
right--;
}
}
}
private void swap(int[] nums, int i ,int j) {
int temp = nums[i];
nums[i] = nums[j];
nums[j] = temp;
}
}
2.Maximum subarray
[Maximum subarray]https://leetcode.com/problems/maximum-subarray/description/
solution:用一个sum和最大值比较
class Solution {
public int maxSubArray(int[] nums) {
int max = Integer.MIN_VALUE;
int sum = 0;
for (int i = 0; i < nums.length; i++) {
sum = sum > 0 ? sum + nums[i] : nums[i];
max = sum > max ? sum : max;
}
return max;
}
}
3.Remove Duplicates from Sorted Array
solution : 排好序的数组,那么重复的元素肯定连在一起。两个指针,一个维护当前数组的长度,一个去遍历数组,跳过重复元素
class Solution {
public int removeDuplicates(int[] nums) {
if (nums == null || nums.length == 0) {
return 0;
}
int size = 1;
for (int i = 1; i < nums.length; i++) {
if (nums[i] != nums[i - 1]) {
nums[size++] = nums[i];//跳过重复元素
}
}
return size;
}
}
4.Maximum SubArray
[连续数组的最大和]https://leetcode.com/problems/maximum-subarray/description/
solution:举例分析数组的规律,例如输入的数组{1,-2,3,10,-4,7,2,-5},试着从头到尾逐个累加示例数组中的每个数字
class Solution {
public int maxSubArray(int[] nums) {
int max = Integer.MIN_VALUE;
int sum = 0;
for (int i = 0; i < nums.length; i++) {
sum = sum > 0 ? sum + nums[i] : nums[i];
max = sum > max ? sum : max;
}
return max;
}
}
5.Majority Elements
[数组中出现次数超过一半的数字]https://leetcode.com/problems/majority-element/description/
solution:数组中有一个数字出现的次数超过了数组长度的一半。那么它出现的次数比其他数字出现次数的和还要多。因此在遍历数组的时候考虑保存两个值,一个是数组中的数字,一个是出现次数。当次数为0的时候,说明前面的数字出现次数正好抵消。那么当nums[i]等于当前值的时候,count + 1,否则-1
class Solution {
public int majorityElement(int[] nums) {
if (nums.length == 0 || nums == null) {
return 0;
}
int current = 0;
int count = 0;
for (int i = 0; i < nums.length; i++) {
if (count == 0) {
current = nums[i];
count += 1;
} else {
if (nums[i] == current) {
count += 1;
} else {
count -= 1;
}
}
}
return current;
}
}
6.数组中重复的数字
[ Find the Duplicate Number]https://leetcode.com/problems/find-the-duplicate-number/description/
solution:我们注意到数组中的数字都在0到n-1的范围内。如果这个数组中没有重复的数字,那么当数组排序之后数字i将出现在下标为i的位置。由于数组中有重复的数字,有些位置可能存在多个数字,同时有些位置可能没有数字。从头到尾依次扫描这个数组中的数字,当扫描到下标为i的数字时,首先比较这个数字(用m表示)是不是等于i,如果是,接着扫描下一个数字。如果不是,拿它和第m个数字比较。如果它和第m个数字相等,就找到了一个重复的数字(该数字的下标为i和m的位置都出现了)。如果它和第m个数字数字不相等,就把第i个数字和第m个数字交换,把m放到属于它的位置。重复。
class Solution {
public int findDuplicate(int[] nums) {
int duplicate = 0;
if (nums.length == 0 || nums == null) {
return 0;
}
for (int i = 0; i < nums.length - 1; i++) {
if (nums[i] < 0 || nums[i] > nums.length - 1) {
return 0;
}
}
for (int i = 0; i < nums.length - 1; i++) {
while (nums[i] != i) {
if (nums[i] == nums[nums[i]]) {
duplicate = nums[i];
return duplicate;
}
// swap nums[i] and nums[nums[i]]
int temp = nums[i];
nums[i] = nums[j];
nums[j] = temp;
}
}
return duplicate;
}
}
Python版:
class Solution:
def findDuplicate(self, nums: List[int]) -> int:
if len(nums) == 0 or nums is None:
return 0
print(nums)
duplicate = 0
temp = 0
# 注意题解,只有一个重复的整数,也就是找到一个就退出好了,因此i始终为0,因为交换操作保证了在当前循环下每个数都可以遇到
for i in range(len(nums)):
while nums[i] != i:
if nums[i] == nums[nums[i]]:
duplicate = nums[i]
return duplicate
# 此处交换即把num[i]的值与nums[temp]的值位置进行交换,此处就是nums[0]
temp = nums[i]
nums[i] = nums[temp]
nums[temp] = temp
return duplicate
7.旋转数组的最小数字
[ Find Minimum in Rotated Sorted Array]https://leetcode.com/problems/find-minimum-in-rotated-sorted-array/description/
solution:二分。用两个指针分别指向数组的第一个元素和最后一个元素。按题目中旋转的规则,第一个元素应该是大于或者等于最后一个元素的(这是一般情况,代码有考虑特殊情况)。接着找到数组中间的元素,如果该中间元素位于前面的递增子数组,那么它应该大于或者等于第一个指针指向的元素。此时数组中最小的元素应该位于中间元素的后面。这样把第一个指针指向该中间元素,缩小范围。同样,如果中间元素位于后面的递增子数组,那么它应该小于或者等于第二个指向的元素,此时该数组中最小的元素应该位于中间元素的前面。这样把第二个指针指向该中间元素,缩小范围。这样,第二个指针指向刚好就是最小的元素,结束循环。其他看注释。
class Solution {
public int findMin(int[] nums) {
if (nums.length == 0 || nums == null) {
return 0;
}
int left = 0;
int right = nums.length - 1;
int mid = left;//排序数组的前面的0个元素搬到最后面,即排序数组本身,也是数组的一个旋转
while (nums[right] <= nums[left]) {
if (right - left == 1) {
mid = right;
break;
}
mid = (left + right) / 2;
//如果下标为left, right和mid指向的三个数字相等,则只能顺序查找
if (nums[left] == nums[right] && nums[mid] == nums[left]) {
return minInOrder(nums, left, right);
}
if (nums[mid] >= nums[left]) {
left = mid;
}
else if (nums[mid] <= nums[right]) {
right = mid;
}
}
return nums[mid];
}
//顺序查找
private int minInOrder(int[] nums, int left, int right) {
int result = nums[left];
for (int i = left; i < right - 1; i++) {
if (nums[i] < result) {
result = nums[i];
}
}
return result;
}
}
8.数组中只出现一次的数字
[Single Number]https://leetcode.com/problems/single-number/description/
solution:可利用异或特有的算数属性,任何一个数字异或它自己都等于0,也就是说,如果从头到尾依次异或数组中的每一个数字,那么最终的结果刚好是那个只出现一次的数字,因为那些出现两次的数字全部在异或中抵消掉了。
class Solution {
public int singleNumber(int[] nums) {
int temp = 0;
for (int i = 0; i < nums.length; i++) {
temp ^= nums[i];
}
return temp;
}
}
进阶:一个整形数组中,每个数字都出现三次,只有一个数字出现一次,找出这个数字。考虑到整形数字在电脑中存储的时候是四个byte,即32位bit,每一个数字都可以表示成一个32位的0 1 串,那么如果用一个32位的数组,记录数组中所有数字,每一位1出现的次数。然后每一位对3取余,数组中不为0的地方就是那一个只出现一次的数字的二进制中1的位置。例如4出现3次。 4的二进制是 0000 0000 0000 0000 0000 0000 0000 0100 4出现三次的话,
统计的数组就把 数组中的第三位记为3,即 数组为 0000 0000 0000 0000 0000 0000 0000 0300,所有位对3取余,结果一定全为0。
class Solution {
public int singleNumber(int[] nums) {
if (nums.length == 0 || nums == null) {
return 0;
}
int bits[] = new int[32]; //定义一个32位数组
for (int i = 0; i < bits.length; i++) {
bits[i]=0; //初始化 数组中所有值为0
}
for (int i = 0; i < nums.length; i++) { //对数组中每一个数字
for (int j = 0; j < bits.length; j++) { //这个数字的每一位
bits[j] += ((nums[i] >> j) & 1);//记录这个位上是否为1,为1的话 bits数组就加1
}
}
int result = 0 ;
for (int j = 0; j < 32; j++) {
if (bits[j] % 3 != 0) //对3取余,是3的倍数的取余后都为0,剩下的就是我们要的
result += (1 << j); //把记录的二进制他转化成整形数字
}
return result;
}
}
举一反三:
- 如果一个数组中,每个数字都出现偶数次,只有一个数字出现一次,利用异或即可
- 如果一个数组中,每个数字都出现奇数(大于1)次,只有一个数字出现一次,那么就用一个32位的数组,记录所有位中为1的个数,最后数组中每一个数字对这个奇数取余,不为0的,把它取出,按它的位数,转化成十进制的数字。
9.数组中第K大的数
[Kth Largest Element in an Array]https://leetcode.com/problems/kth-largest-element-in-an-array/description/
solution:如果考虑海量数据和内存不够的话,采用最小堆(
class Solution {
public int findKthLargest(int[] nums, int k) {
if (k <= 0 || nums.length == 0 || nums == null) {
return 0;
}
PriorityQueue<Integer> minHeap = new PriorityQueue<Integer>();
for (int i = 0; i < nums.length; i++) {
minHeap.add(nums[i]);
if (minHeap.size() > k) {
minHeap.poll();
}
}
return minHeap.poll();
}
}
solution:常规思路,partition思想
10.把数组排成最小的数
[Largest Number]https://leetcode.com/problems/largest-number/description/
solution:先把整形数组转换成String数组,然后将String数组排序,最后将排好序的字符串数组拼接出来。关键就是制定排序规则。排序规则如下:
若ab > ba,则a > b
若ab < ba,则a < b
若ab = ba,则a = b
import java.util.*;
class Solution {
public String largestNumber(int[] nums) {
if (nums == null || nums.length == 0) {
return "";
}
int n = nums.length;
String[] str = new String[n];
StringBuilder sb = new StringBulider();
//数组转化为字符串
for (int i = 0; i < n; i++) {
str[i] = String.valueOf(nums[i]);
}
Arrays.sort(str, new Comparator<String>() {
@Override
public int compare(String s1, String s2) {
String c1 = s1 + s2;
String c2 = s2 + s1;
return c1.compareTo(c2);
}
}
);
for (int i = 0; i < n; i++) {
sb.append(str[i]);
}
return sb.toString();
}
}
11.二维数组中的查找
[Search a 2D Matrix]https://leetcode.com/problems/search-a-2d-matrix/description/
solutions:由于二维数组是排好序的,既行从左到右递增,列从上到下递增。因此每次选取右上角的元素与目标数字进行比较。如果大于目标数字,那么目标只可能在左边,于是删掉右上角元素所在的列
class Solution {
public boolean searchMatrix(int[][] matrix, int target) {
if(matrix == null || matrix.length == 0){
return false;
}
if(matrix[0] == null || matrix[0].length == 0){
return false;
}
int m = matrix.length;
int n = matrix[0].length;
int i = 0;
int j = n - 1;
while (i < m && j >= 0) {
//i,j的定义已经在右上角了
if (matrix[i][j] == target) {
return true;
} else if (matrix[i][j] > target) {
j--;
} else {
i++;
}
}
return false;
}
}
12.数组中的逆序对
[ Count of Smaller Numbers After Self]https://leetcode.com/problems/count-of-smaller-numbers-after-self/description/
solution:先把数组分成子数组,先统计出子数组内部的逆序对的数目,然后再统计出两个相邻子数组之间的逆序对的数目。在统计逆序对的过程中,还需要对数组排序,归并排序。
剑指offer解法
public static int inversePairs(int[] data) {
if (data == null || data.length < 1) {
throw new IllegalArgumentException("Array arg should contain at least a value");
}
int[] copy = new int[data.length];
System.arraycopy(data, 0, copy, 0, data.length);
return inversePairsCore(data, copy, 0, data.length - 1);
}
private static int inversePairsCore(int[] data, int[] copy, int start, int end) {
if (start == end) {
copy[start] = data[start];
return 0;
}
int length = (end - start) / 2;
int left = inversePairsCore(copy, data, start, start + length);
int right = inversePairsCore(copy, data, start + length + 1, end);
// 前半段的最后一个数字的下标
int i = start + length;
// 后半段最后一个数字的下标
int j = end;
// 开始拷贝的位置
int indexCopy = end;
// 逆序数
int count = 0;
while (i >= start && j >= start + length + 1) {
if (data[i] > data[j]) {
copy[indexCopy] = data[i];
indexCopy--;
i--;
count += j - (start + length); // 对应的逆序数
} else {
copy[indexCopy] = data[j];
indexCopy--;
j--;
}
}
for (; i >= start;) {
copy[indexCopy] = data[i];
indexCopy--;
i--;
}
for (; j >= start + length + 1;) {
copy[indexCopy] = data[j];
indexCopy--;
j--;
}
return count + left + right;
}
LeetCode变形解法https://leetcode.com/problems/count-of-smaller-numbers-after-self/description/
solution:Traverse from the back to the beginning of the array, maintain an sorted array of numbers have been visited. Use findIndex() to find the first element in the sorted array which is larger or equal to target number. For example, [5,2,3,6,1], when we reach 2, we have a sorted array[1,3,6], findIndex() returns 1, which is the index where 2 should be inserted and is also the number smaller than 2. Then we insert 2 into the sorted array to form [1,2,3,6].
class Solution {
public List<Integer> countSmaller(int[] nums) {
Integer[] ans = new Integer[nums.length];
List<Integer> sorted = new ArrayList<Integer>();
for (int i = nums.length - 1; i >= 0; i--) {
int index = findIndex(sorted, nums[i]);
ans[i] = index;
sorted.add(index, nums[i]);
}
return Arrays.asList(ans);
}
private int findIndex(List<Integer> sorted, int target) {
if (sorted.size() == 0) return 0;
int start = 0;
int end = sorted.size() - 1;
if (sorted.get(end) < target) return end + 1;
if (sorted.get(start) >= target) return 0;
while (start + 1 < end) {
int mid = start + (end - start) / 2;
if (sorted.get(mid) < target) {
start = mid + 1;
} else {
end = mid;
}
}
if (sorted.get(start) >= target) return start;
return end;
}
}
- 乱序数组中的最长递增子序列
题型A:输出最长递增子序列的数组(即包含的数)
solution:给出两种解法,普通的dp,dp + 二分。具体看注释:
public class Problem_05_LIS {
public static int[] lis1(int[] arr) {
if (arr == null || arr.length == 0) {
return null;
}
int[] dp = getdp1(arr);
return generateLIS(arr, dp);
}
//dp中放的是,对应数组中每个元素为输出序列的最后一个值时的最大长度
//方法一:O(n^2)复杂度,具体思路是:两层循环,外层为i时,内从从0到i,依次判断,
//当加入 arr[i]的值后,更新dp中最长子序列的长度,最长子序列更新,依赖于i前面的dp
//中的数组值,若是arr[i]比i之前的某个位置上的的数要大,则通过max(dp[i], dp[j] + 1);
//来更新第i位上的值。
public static int[] getdp1(int[] arr) {
int[] dp = new int[arr.length];
for (int i = 0; i < arr.length; i++) {
dp[i] = 1;
for (int j = 0; j < i; j++) {
if (arr[i] > arr[j]) {
//如果arr[i]比arr[j]要大,说明递增最长子序列要增加
dp[i] = Math.max(dp[i], dp[j] + 1);
//plus 1 since strictly speaking we need to count nodes on the paths
}
}
}
return dp;
}
//根据 dp 数组的值生成要打印的数组,作为最后结果的输出,
public static int[] generateLIS(int[] arr, int[] dp) {
int len = 0;
int index = 0;
for (int i = 0; i < dp.length; i++) {
if (dp[i] > len) {
len = dp[i];
index = i;
}
}
int[] lis = new int[len];
lis[--len] = arr[index];
for (int i = index; i >= 0; i--) {
if (arr[i] < arr[index] && dp[i] == dp[index] - 1) {
lis[--len] = arr[i];
index = i;
}
}
return lis;
}
}
public static int[] lis2(int[] arr) {
if (arr == null || arr.length == 0) {
return null;
}
int[] dp = getdp2(arr);
return generateLIS(arr, dp);
}
//方法二,时间复杂度为O(N*logN),这个时间复杂度具体体现在一个for循环里套了一个二分查找
//dp中放的是,对应数组中每个元素为输出序列的最后一个值时的最大长度
//ends数组作为一个辅助数组,存放的是有效区的数值,ends[i]的含义是:
//当遍历到当前的数时,ends[i]表示的是,遍历到当前时刻,长度为i+1的子序列中的末尾元素值
//每次更新的是长度为i+1子序列的末尾元素的值,那么,可以通过二分法查找每个当前遍历到的
//元素在ends中应该所处的位置
//然后更新对应的位置上的元素值,然后根据i+1,就可以知道,当前元素作为子序列的末尾元素时,
//前面有几个数了(i)
//程序中的right值记录的是,有效区数组ends的长度,l为左边界,r为右边界,m为中间值
public static int[] getdp2(int[] arr) {
int[] dp = new int[arr.length];
int[] ends = new int[arr.length];
ends[0] = arr[0];
dp[0] = 1;
int right = 0;
int l = 0;
int r = 0;
int m = 0;
for (int i = 1; i < arr.length; i++) {
l = 0;
r = right;
while (l <= r) {
m = (l + r) / 2;
if (arr[i] > ends[m]) {
l = m + 1;
} else {
r = m - 1;
}
}
right = Math.max(right, l); //更新有效区数组的边界值,若是所有的元素都小于当前的元素,则数组值往外扩充一个
ends[l] = arr[i];
dp[i] = l + 1;
}
return dp;
}
[LeetCode版]https://leetcode.com/problems/longest-increasing-subsequence/description/ 只需返回最大长度即可,dp也可以做,但是直接使用二分更为简便。
class Solution {
public int lengthOfLIS(int[] nums) {
if (nums == null || nums.length == 0) {
return 0;
}
int[] tails = new int[nums.length];
int size = 0;
for (int x : nums) {
int i = 0;
int j = size;
while (i != j) {
int m = (i + j) / 2;
if (tails[m] < x) {
i = m + 1;
} else {
j = m;
}
}
tails[i] = x;
if (i == size) {
++size;
}
}
return size;
}
}
通俗解法:
public class solution {
public int lengthOfLIS(int[] nums) {
if (nums == null || nums.length == 0) {
return 0;
}
int[] dp = new int[nums.length];
dp[0] = nums[0];
int len = 0;
for (int i = 0; i < nums.length; i++) {
//dp为辅助数组,第一个数字nums[0]放进去之后,形成第一个有效区
//然后接下来的nums[i]在dp数组中寻找第一个比nums[i]自己大的数(二分)
//dp[i]代表长度为i + 1的最长递增子序列的最小末尾
int pos = binarySearch(dp, len, nums[i]);
if (nums[i] < dp[pos]) {
dp[pos] = nums[i];
}
if (pos > len) {
len = pos;
dp[len] = nums[i];
}
}
return len + 1;
//return dp;
}
private int binarySearch(int[] dp, int len, int val) {
int left = 0;
int right = len;
while (left + 1 < right) {
int mid = left + (right - left) / 2;
if (dp[mid] == val) {
return mid;
} else {
if (dp[mid] < val) {
left = mid;
} else {
right = mid;
}
}
}
if (dp[right] < val) {
return len + 1;
} else if (dp[left] >= val) {
return left;
} else {
return right;
}
}
}
附上二分查找的实现代码:
//二分基本思想:
//将n个元素分成个数大致相同的两半,取a[n/2]与欲查找的x作比较,
//如果x=a[n/2]则找到x,算法终止。如果x<a[n/2],
//则我们只要在数组a的左半部继续搜索x(这里假设数组元素呈升序排列)
//如果x>a[n/2],则我们只要在数组a的右 半部继续搜索x
//非递归
class BinarySearch {
public static int binarySearch(int[] nums, int low , int high, int value) {
//value是欲查找的值
int left = low;
int right = high - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (value < nums[mid]) {
high = mid - 1;
} else if (value > nums[mid]) {
low = mid + 1;
} else {
return mid;
}
}
return - 1;
}
}
//递归
class BinarySearch {
public static int binarySearch(int[] nums, int low, int high, int value) {
if (low > high) {
return -1;
}
int mid = left + (right - left) / 2;
if (value < nums[mid]) {
return binarySearch(nums, low, mid - 1, value);
} else if(value > nums[mid]) {
return binarySearch(nums, mid + 1, high, value);
} else {
return mid;
}
}
}
- 数组的全排列
[Permutations]https://leetcode.com/problems/permutations/description/
solution:递归,DFS。题意:给定一个数组,返回这个数组可能出现的所有组合。两个列表形式(结果集,加小子集)。具体做法看注释。
public class Solution {
List<List<Integer>> res = new ArrayList<List<Integer>>();
public List<List<Integer>> permute(int[] nums) {
int len = nums.length;
if (nums == null || len == 0) {
return res;
}
//前后交换元素
exchange(nums, 0, len);
return res;
}
public void exchange(int[] nums, int i, int len) {
//当前数组加到结果集中
//如果已经到最后一个元素,前面的数已经排好,输出
if (i == len - 1) {
//注意这里list与上面res的写法不同之处
List<Integer> list = new ArrayList<>();
for (int j = 0; j < len; j++) {
list.add(nums[j]);
}
res.add(list);
return;
}
//思考场景,定了第一个数之后后面的解已经有了。那么交换前后元素
//从当前位置的数跟后面的数交换
for (int j = i; j < len; j++) {
swap(nums, i, j);
exchange(nums, i + 1, len);
swap(nums, i, j);
}
}
public void swap(int[] nums, int i, int j) {
int temp = nums[i];
nums[i] = nums[j];
nums[j] = temp;
}
}
15、搜索旋转数组
搜索旋转排序数组
solution:
题目要求O(logN)的时间复杂度,基本可以断定本题是需要使用二分查找,怎么分是关键
由于题目说数字了无重复,举个例子
1 2 3 4 5 6 7 可以大致分为两类,
第一类 2 3 4 5 6 7 1这种,也就是nums[start] <= nums[mid]。此例子中就是2 <= 5。这种情况下,前半部分有序。因此如果 nums[start] <=target<nums[mid]。则在前半部分找,否则去后半部分找。
第二类 6 7 1 2 3 4 5这种,也就是nums[start] > nums[mid]。此例子中就是6 > 2
这种情况下,后半部分有序。因此如果 nums[mid] <target<=nums[end]。则在后半部分找,否则去前半部分找。
class Solution:
def search(self, nums: List[int], target: int) -> int:
if len(nums) == 0 or nums is None:
return -1
left = 0
right = len(nums) - 1
while left <= right:
mid = left + (right - left) // 2
if nums[mid] == target:
return mid
if nums[left] <= nums[mid]:
if target <= nums[mid] and target >= nums[left]:
right = mid - 1
else:
left = mid + 1
else:
if target >= nums[mid] and target <= nums[right]:
left = mid + 1
else:
right = mid - 1
return -1