Leetcode Tags(2)Array
一、448. Find All Numbers Disappeared in an Array
给定一个范围在 1 ≤ a[i] ≤ n ( n = 数组大小 ) 的 整型数组,数组中的元素一些出现了两次,另一些只出现一次。 找到所有在 [1, n] 范围之间没有出现在数组中的数字。 您能在不使用额外空间且时间复杂度为O(n)的情况下完成这个任务吗? 你可以假定返回的数组不算在额外空间内。 输入:[4,3,2,7,8,2,3,1] 输出:[5,6]
1.算法思路(本题关键点:注意数组下标的范围和要求的数之间有什么联系。)
既然要遍历[1,n]中的数,由于数组中的长度已经保证了为n,因此数组的下标加上1就正好满足从1到n,因此,要对下标做文章。
2.代码实现
将[4,3,2,7,8,2,3,1]变成[-4,-3,-2,-7,8,2,-3,-1]然后遍历到8和2这两个正数时,就将数组下标加上1就是要求的结果了。
public List<Integer> findDisappearedNumbers(int[] nums) { List<Integer> list = new ArrayList<>(); for (int i = 0; i < nums.length; i++) { int val = Math.abs(nums[i]) - 1; if (nums[val] > 0) nums[val] = -nums[val];// 如果不进行判断,那么2和3就会反转两次,还是变成2和3,最后结果为[2,3,5,6] } for (int i = 0; i < nums.length; i++) { if (nums[i] > 0) list.add(i+1); } return list; }
二、283. Move Zeroes
Input: [0,1,0,3,12]
Output: [1,3,12,0,0]
1.算法思路:双指针,前一个指针指的是数组的索引,后一个指针指的是非零元素的索引
2.代码实现
(1)最普通的思路最清晰的双指针
public void moveZeroes(int[] nums) { int prev = 0; int curr = 0; while (curr < nums.length) { if (nums[prev] == 0 && nums[curr] != 0) { nums[prev] = nums[curr]; nums[curr] = 0; prev++; curr++; } else if (nums[prev] != 0 && nums[curr] == 0) { prev++; curr++; } else if (nums[prev] != 0 && nums[curr] != 0) { prev++; curr++; } else { curr++; } } }
(2)高级的双指针写法1,(关键点:nums[lastNonZeroFoundAt++]的写法)同样使用指针lastNonZeroFoundAt和指针i。
void moveZeroes(vector<int>& nums) { int lastNonZeroFoundAt = 0; for (int i = 0; i < nums.size(); i++) { if (nums[i] != 0) nums[lastNonZeroFoundAt++] = nums[i]; } for (int i = lastNonZeroFoundAt; i < nums.size(); i++) nums[i] = 0; }
(3)高级的双指针写法2
void moveZeroes(vector<int>& nums) { for (int lastNonZeroFoundAt = 0, cur = 0; cur < nums.size(); cur++) { if (nums[cur] != 0) swap(nums[lastNonZeroFoundAt++], nums[cur]); } }
三、(二维数组--九宫格问题)661. 图片平滑器
输入: [[1,1,1], [1,0,1], [1,1,1]] 输出: [[0, 0, 0], [0, 0, 0], [0, 0, 0]] 解释: 对于点 (0,0), (0,2), (2,0), (2,2): 平均(3/4) = 平均(0.75) = 0 对于点 (0,1), (1,0), (1,2), (2,1): 平均(5/6) = 平均(0.83333333) = 0 对于点 (1,1): 平均(8/9) = 平均(0.88888889) = 0
1.算法思路
之前的思路是为了防止计算的时候数周围的数没有九个,因此建立一个比原始矩阵大一圈的矩阵,然后将原来的矩阵复制到大矩阵的中间。也就是在原矩阵四周圈上一圈0;
但是有可以不用新建矩阵的方法。(关键点:在做加法之前先判断索引是否有效,如果有效才进行加法计算)
2.代码实现
public int[][] imageSmoother(int[][] M) { int[][] res = new int[M.length][M[0].length]; for (int i = 0; i < M.length; i++) { for (int j = 0; j < M[0].length; j++) { int count = 0; for (int x = i - 1; x <= i + 1; x++) { for (int y = j - 1; y <= j + 1; y++) { if (0 <= x && x < M.length && 0 <= y && y < M[0].length) { res[i][j] += M[x][y]; count++; } } } res[i][j] /= count; } } return res; }
四、(双指针问题,单个指针for循环)830. Positions of Large Groups
Input: "abcdddeeeeaabbbcd"
Output: [[3,5],[6,9],[12,14]]
1.解法思路
使用prev指针指向子字符串的起始位置,使用curr指向子字符串的终止位置,如果curr和prev的距离相差3就添加到列表中。
2.代码实现(关键点:prev待定,curr指针for循环,如果当前字符和下一字符相等就什么也不做,for循环里面curr会自动加1向后移动)
public List<List<Integer>> largeGroupPositions(String S) { int prev = 0; List<List<Integer>> result = new ArrayList<>(); for (int curr = 0; curr < S.length(); curr++) { if (curr == S.length() - 1 || S.charAt(curr) != S.charAt(curr+1)) { if (curr-prev+1 >= 3) { result.add(Arrays.asList(new Integer[] {prev, curr})); } prev = curr + 1; } } return result; }
五、628. Maximum Product of Three Numbers
Input: [1,2,3,4]
Output: 24
1.解法思路
(1)常规算法,排序,由于可能存在负数的原因,而两个负数乘起来就是负数,例如{-4,-3,-2,-1,60},因此需要比较排序后的Math.max(nums[nums.length-1] * nums[nums.length-2] * nums[nums.length-3], nums[0] * nums[1] * nums[nums.length-1]);
时间复杂度O(nlog(n)),空间复杂度O(log(n)
(2)更快的算法,按照自己的规则手动进行排序,时间复杂度O(n),空间复杂度O(1)
public int maximumProduct(int[] nums) { int min1 = Integer.MAX_VALUE, min2 = Integer.MAX_VALUE; int max1 = Integer.MIN_VALUE, max2 = Integer.MIN_VALUE, max3 = Integer.MIN_VALUE; for (int n: nums) { if (n <= min1) { min2 = min1; min1 = n; } else if (n <= min2) { // n lies between min1 and min2 min2 = n; } if (n >= max1) { // n is greater than max1, max2 and max3 max3 = max2; max2 = max1; max1 = n; } else if (n >= max2) { // n lies betweeen max1 and max2 max3 = max2; max2 = n; } else if (n >= max3) { // n lies betwen max2 and max3 max3 = n; } } return Math.max(min1 * min2 * max1, max1 * max2 * max3); }
六、(数组插入问题)(三指针问题)88. Merge Sorted Array
Input: nums1 = [1,2,3,0,0,0], m = 3 nums2 = [2,5,6], n = 3 Output: [1,2,2,3,5,6]
1.解题思路
由于数组插入数据的困难性,再要求将nums中的数插入到nums1的合适位置中时,可以从后往前比较,将最大的数放在nums1的最末位,一次比较。
2.解题代码(注意点:如果nums2中的数全都先于nums1插入完成,那么nums1中无序改变了;如果nums1中的数全部都移向了nums1的后面,那么nums1前面的数将由nums2中的数依次填充。)
public void merge(int[] nums1, int m, int[] nums2, int n) { int i = m - 1; int j = n - 1; int k = m + n - 1; while (i >= 0 && j >= 0) { if (nums1[i] > nums2[j]) nums1[k--] = nums1[i--]; else nums1[k--] = nums2[j--]; } while (j >= 0) nums1[k--] = nums2[j--]; }
七、(放花问题)605. Can Place Flowers
假设你有一个很长的花坛,一部分地块种植了花,另一部分却没有。可是,花卉不能种植在相邻的地块上,它们会争夺水源,两者都会死去。 输入: flowerbed = [1,0,0,0,1], n = 1 输出: True 输入: flowerbed = [1,0,0,0,1], n = 2 输出: False
1.解题思路(关键点:找出位置之后,直接种上花,就不用再烦恼连续的0能种多少花的问题。)
和前面的二维数组的边界问题一样,可以在if判断语句上面添加对索引的限制。
遍历一次,如果存在值为0且左右都为0,那么count加1并且将该位置设为1,即种上花。
2.代码实现(关键点:解决第一个位置和最后一个位置的问题,这种关系表达式的表达技巧)
public boolean canPlaceFlowers(int[] flowerbed, int n) { int count = 0, i = 0; while (i < flowerbed.length) { if (flowerbed[i] == 0 && (i == 0 || flowerbed[i-1] == 0) && (i == flowerbed.length-1 || flowerbed[i+1] == 0)) { count++; flowerbed[i] = 1; } i++; } return count <= n; }
七点五、(坐座位问题)849. Maximize Distance to Closest Person
输入:[1,0,0,0,1,0,1] 输出:2 解释:如果亚历克斯坐在第二个空位(seats[2])上,他到离他最近的人的距离为 2 。果亚历克斯坐在其它任何一个空位上,他到离他最近的人的距离为 1 。 因此,他到离他最近的人的最大距离是 2 。
1.思路:和上面的养花问题一样,无非就是寻找距离1最远距离的位置,同样存在边界问题。
2.解法
public int maxDistToClosest(int[] seats) { int N = seats.length; int prev = -1, future = 0; int ans = 0; for (int i = 0; i < N; ++i) { if (seats[i] == 1) { prev = i; } else { while (future < N && seats[future] == 0 || future < i) future++; int left = prev == -1 ? N : i - prev; int right = future == N ? N : future - i; ans = Math.max(ans, Math.min(left, right)); } } return ans; }
public int maxDistToClosest(int[] seats) { int N = seats.length; int K = 0; //current longest group of empty seats int ans = 0; for (int i = 0; i < N; ++i) { if (seats[i] == 1) { K = 0; } else { K++; ans = Math.max(ans, (K + 1) / 2); } } for (int i = 0; i < N; ++i) if (seats[i] == 1) { ans = Math.max(ans, i); break; } for (int i = N-1; i >= 0; --i) if (seats[i] == 1) { ans = Math.max(ans, N - 1 - i); break; } return ans; }
八、(最短无序连续子数组)581. Shortest Unsorted Continuous Subarray(TODO)
输入: [2, 6, 4, 8, 10, 9, 15] 输出: 5 解释: 你只需要对 [6, 4, 8, 10, 9] 进行升序排序,那么整个表都会变为升序排序。
1.思路:
九、(int和Integer的问题)414. Third Maximum Number
Input: [3, 2, 1] Output: 1 Explanation: The third maximum is 1. Input: [1, 2] Output: 2 Explanation: The third maximum does not exist, so the maximum (2) is returned instead. Input: [2, 2, 3, 1] Output: 1 Explanation: Note that the third maximum here means the third maximum distinct number. Both numbers with value 2 are both considered as second maximum.
1.解题思路:上面的有一题也是按照自己的顺序对数组进行一次遍历,然后排序找出了5个数。这里也可以用同样的原理:
2.有问题的代码,在定义了int类型的Integer.MIN_VALUE后,如果输入是{2,1,Integer.MIN_VALUE}那么根据return语句可以知道返回结果并不是Integer.MIN_VALUE,这里就有了一个问题。自己想的解决思路是再设置三个标志位,如果输入Integer.MIN_VALUE确实修改了就将标志位设置为true,但是这样会造成额外的开销。
public int thirdMax(int[] nums) { int first = Integer.MIN_VALUE, second = Integer.MIN_VALUE, third = Integer.MIN_VALUE; for (int i : nums) { if (i < second && i > third) { third = i; } else if (first > i && i > second) { third = second; second = i; } else if (i > first) { third = second; second = first; first = i; } } return third == Integer.MIN_VALUE ? first : third; }
3.解决问题的方法:将int类型改成Integer类型即可,其他完全一样,这样在return中就可以使用third==null?来进行判断。
不过由于考虑到初始时first,second,third为null时直接替换,在if条件中加上了first == null ||与条件,这就会发生另外一种情况,就是{2,2,3,1}时,第二个2会直接进入到second,也就是无法去除重复元素,因此需要在前面加上一条判断语句, if (i.equals(first) || i.equals(second) || i.equals(third)) continue;,即如果和三个中的其中一个相同就直接退出循环。
public int thirdMax(int[] nums) { Integer first = null, second = null, third = null; for (Integer i : nums) { if (i.equals(first) || i.equals(second) || i.equals(third)) continue; if (first == null || i > first) { third = second; second = first; first = i; } else if (second == null || (first > i && i > second)) { third = second; second = i; } else if (third == null || second > i && i > third) { third = i; } } return third == null ? first : third; }
十、(考虑完各种情况)665. Non-decreasing Array
输入: [4,2,3] 输出: True 解释: 你可以通过把第一个4变成1来使得它成为一个非递减数列。 输入: [4,2,1] 输出: False 解释: 你不能在只改变一个元素的情况下将其变为非递减数列。
1.思路:主要是要分清各种情况
首先,定位到A[p-1]>A[p],如果p不是唯一的或者不存在,那么就是false。
定位到之后,分析下面的情况:
- p == -1:整个数组本身就是非递减数列。
- p == 1:也就是nums的nums[0] > nums[1],这个时候,只要让nums[0] = nums[1]
- p == nums.length - 1:也就是nums的nums[nums.length - 2] > nums[nums.length - 1],这个时候,只要让最后一个点的值大于或等于倒数第二个点的值即可。
- 如果nums[p-2]、nums[p-1]、nums[p]、nums[p+1]都存在的情况下:
- nums[p-2] <= nums[p]:类似于{1,3,2,4}这种情况,可以把3变成1和2之间的数。
- nums[p-1] <= nums[p+1]:类似于{1,3,2,4}这种情况,也可以把2变成3和4之间的数。
2.代码:需要注意的是,由于最多只能改变一个数的值,因此,
for循环中的if-else语句就是判断是否有两个或两个以上的值满足要求,如果是,就返回false。也就是说,p只能被赋值一次!
public boolean checkPossibility(int[] nums) { int p = -1; for (int i = 1; i < nums.length; i++) { if (nums[i-1] > nums[i]) { if (p != - 1) return false; else p = i; } } return (p == -1 || p == 1 || p == nums.length-1 || nums[p-2] <= nums[p] || nums[p-1] <= nums[p+1]); }
十一、739. Daily Temperatures
十二、565.Array Nesting 854 / 856 test cases passed
输入: A = [5,4,0,3,1,6,2] 输出: 4 解释: A[0] = 5, A[1] = 4, A[2] = 0, A[3] = 3, A[4] = 1, A[5] = 6, A[6] = 2. 其中一种最长的 S[K]: S[0] = {A[0], A[5], A[6], A[2]} = {5, 6, 2, 0}
1.常规做法(O(n2) + O(1)),854 / 856 test cases passed,Time Limit Exceeded
public int arrayNesting(int[] nums) { int maxlen = 0; int len = 0; int tmp = 0; for (int i = 0; i < nums.length; i++) { len = 1; tmp = nums[i]; while (nums[tmp] != nums[i]) { len++; tmp = nums[tmp]; } maxlen = Math.max(maxlen, len); } return maxlen; }
2.更快的做法1(O(n) + O(n)):Using Visited Array
思路就是,例如A = [5,4,0,3,1,6,2],如果我们在A[0]=5的过程中已经确定了5, 6, 2, 0,那么我们将0,2,5,6这些位置进行标记,之后在遍历到2/5/6时直接跳过即可,
public int arrayNesting(int[] nums) { boolean[] visited = new boolean[nums.length]; int res = 0; for (int i = 0; i < nums.length; i++) { if (!visited[i]) { int start = nums[i], count = 0; do { start = nums[start]; count++; visited[start] = true; } while (start != nums[i]); res = Math.max(res, count); } } return res; }
3.更快的做法2(O(n) + O(1)):Without Using Extra Space
思路就是,例如A = [5,4,0,3,1,6,2],如果我们在A[0]=5的过程中已经确定了5, 6, 2, 0,那么2/5/6在之后的遍历过程中可以不用了,也就是在原数组中修改这些位置上的值即可,反正也再也用不到了。
public int arrayNesting(int[] nums) { int res = 0; for (int i = 0; i < nums.length; i++) { if (nums[i] != Integer.MAX_VALUE) { int start = nums[i], count = 0; while (nums[start] != Integer.MAX_VALUE) { int temp = start; start = nums[start]; count++; nums[temp] = Integer.MAX_VALUE; } res = Math.max(res, count); } } return res; }
十三、medium 529(DFS+BFS)
1.
public char[][] updateBoard(char[][] board, int[] click) { int m = board.length, n = board[0].length; int row = click[0], col = click[1]; if (board[row][col] == 'M') { board[row][col] = 'X'; } else { int count = 0; for (int i = -1; i < 2; i++) { for (int j = -1; j < 2; j++) { if (i == 0 && j == 0) continue; int r = row + i, c = col + j; if (r < 0 || r >= m || c < 0 || c >= n) continue; if (board[r][c] == 'M' || board[r][c] == 'X') count++; } } if (count > 0) { board[row][col] = (char)(count + '0'); } else { board[row][col] = 'B'; for (int i = -1; i < 2; i++) { for (int j = -1; j < 2; j++) { if (i == 0 && j == 0) continue; int r = row + i, c = col + j; if (r < 0 || r >= m || c < 0 || c < 0 || c >= n) continue; if (board[r][c] == 'E') updateBoard(board, new int[] {r, c}); } } } } return board; }
十四、