重刷Leetcode
前言:之前用C++把代码随想录写的差不多了,近期在学Java,写着重写一遍题目,在复习算法的同时,顺便掌握一下Java的语法。 本贴打算作为一个长期贴,并且每道题只做提示,不会发详细的题解。旨在快速复习题目,看有没有考虑一些特殊情况。
704. 二分查找(优先级、闭开区间问题)
- 注意到
int mid = (left + right)/2
中加法会溢出的问题。于是我写成了int mid = low + (high - low) >> 1;
。错误,优先级错误,因为位移操作符 >> 的优先级比加法 + 低。 - 注意闭、开区间的写法。判断条件,赋值都会有所不同。
27. 移除元素(循环代码简化)
移除元素
每次太久没做这道题,回来做总会写成
class Solution {
public int removeElement(int[] nums, int val) {
int p = 0, q = 0;
while(q < nums.length) {
while(q < nums.length && nums[q] == val) {
q++;
}
if(q >= nums.length) {
break;
}
nums[p++] = nums[q++];
}
return p;
}
}
虽然思路是一样的,时间复杂度一样。但是明显比下面答案冗长的多。
class Solution {
public int removeElement(int[] nums, int val) {
int fast = 0, slow = 0;
while(fast < nums.length) {
if(nums[fast] == val) {
fast++;
continue;
}
nums[slow++] = nums[fast++];
}
return slow;
}
}
反思了一下,写成第一种是因为总是把一个过程看太复杂
了,即一个循环内完成一次找到合适元素的过程。而第二种写法是一步一步找的过程。
所以如果遇到写的过程发现思路是对的,但是代码很冗余。这种情况可以想一下是不是可以简化一次循环过程。
977.有序数组的平方(二分查找运用和java库函数binarySearch)
1.最简单的方法就是先平方后排序,时间复杂度为O(N logN)
(因为快排)。这种方法没有利用到有序
的信息。可以先用二分查找(自己实现)找出正负分界点
(这一部分是检测能否熟练掌握二分查找的技能),然后再使用类似归并的思想处理。时间复杂度为O(N)
。
当然,二分查找也有对应的库,介绍一下Arrays.binarySearch
的用法。
binarySearch(Object[] a, Object key)
- 如果可以找到:返回一个 >=0 的索引
- 如果找不到,则返回负数(-要插入的位置),注意这里“要插入的位置”是从1开始的。
对应代码如下:
int bound = Arrays.binarySearch(nums, 0);
if(bound < 0) {
bound = -bound-1;
}
// 此时bound表示0或第一个正数
// 数组分为[,bound) 和[bound,)
- 除了上面两种方法,官方答案(法三)还给出不用一开始查找分界点的方法。自己思考回忆一下…
- leetcode官方给出的空间复杂度我不太赞同,他是开答案的空间就不算入空间复杂度。这样看的话原地处理(法一)并没有优势。
- 在写归并时,最后两个while循环中,我写了下面代码,然后报错了,原因是超出数组范围。
while(q<nums.length) {
res[index++] = nums[q++] * nums[q];
}
原因:q++放前面了,所以两个q并不是一个值。正确代码为:res[index++] = nums[q] * nums[q++];
【标记】209. 长度最小的子数组(1.窗口和2.前缀和 + 二分查找)
长度最小的子数组
1.还是代码简化的问题。用窗口解决的时候,我写出了以下代码:
class Solution {
public int minSubArrayLen(int target, int[] nums) {
int start = 0, end = 0;
int sum = nums[0];
int res = Integer.MAX_VALUE;
while(end < nums.length) {
if(sum >= target) {
res = Math.min(res, end - start + 1);
sum -= nums[start];
start++;
// 实际上这一部分是可以去掉的
//if(start > end && start < nums.length) {
// end = start;
// sum = nums[start];
//}
}else {
end++;
if(end < nums.length) sum += nums[end];
}
}
return res == Integer.MAX_VALUE? 0: res;
}
}
所以循环体里面变成了
while(end < nums.length) {
if(sum >= target) {
res = Math.min(res, end - start + 1);
sum -= nums[start];
start++;
}else {
end++;
if(end < nums.length) sum += nums[end];
}
}
这个代码看起来还是比较"冗余",end < nums.length
居然要判断两次,如何让一次循环中保证end
没越界呢?如果能够让end++
后没有对end
操作,就不用再次判断了。则sum += nums[end];
要放在前面。
所以逻辑很清晰了,也就是每次循环进行sum += nums[end];
,即每次循环处理start
值,尽量缩小窗口,缩到不满足题目条件时(需要增大窗口时),进入下一个循环。于是有了以下代码。
class Solution {
public int minSubArrayLen(int target, int[] nums) {
int start = 0, end = 0;
int sum = 0; //sum值就不能一开始赋值了
int res = Integer.MAX_VALUE;
while(end < nums.length) {
sum += nums[end]; // 选择在这里维护窗口里面的值
// 下面循环需要缩小窗口,直到需要增大窗口。
while(sum >= target) {
res = Math.min(res, end - start + 1);
sum -= nums[start];
start++;
}
end++;
}
return res == Integer.MAX_VALUE? 0: res;
}
}
代码简化以后就不贴太多代码了,主要还是理解思想,后面如果有比较巧妙的简化方法,我再贴出来。
2.官方解答中还给出介于暴力与窗口之间的解法(前缀和 + 二分查找)(这里数组没有有序,是怎么用到二分查找的,自己想一想)
。贴个老哥的回答供参考。最好自己推导理解一下。
其中关于区间和:可以参考链接
59.螺旋矩阵II 和54. 螺旋矩阵 (循环方式,注意开闭区间)
1.注意while循环判断中,while(left<right && up<down)
可以解决第一个题目,但是第二个题目不行。分析后觉得,最好是while(true)
然后跳出循环的方式放在每次改变边界的时候,例如:
while(true) {
for(int i=left; i<right;i++) {
res[up][i] = index++;
}
if(++up >= down) break;
for(int j=up;j<down;j++) {
res[j][right-1] = index++;
}
if(--right <= left) break;
for(int i=right-1;i>=left;i--) {
res[down-1][i] = index++;
}
if(--down <= up) break;
for(int j=down-1;j>=up;j--) {
res[j][left] = index++;
}
if(++left >= right) break;
}
(待解决)44. 开发商购买土地(区间和)
203. 移除链表元素(递归和迭代)
题目链接
1.实际上,删除链表元素只需要一个指针即可。所以写的时候不用一个前pre
一个后p
。
while(pre.next != null) {
if(pre.next.val == val) {
pre.next = pre.next.next;
}else {
pre = pre.next;
}
}
这样即可删除。
2. 递归太久没做真的难想,不过看一遍就知道了。
class Solution {
public ListNode removeElements(ListNode head, int val) {
if(head == null) {
return head;
}
head.next = removeElements(head.next, val);
return head.val==val?head.next:head;
}
}
707. 设计链表(尾指针维护)
题目链接
1.可以用单链表/双链表维护。
2.注意增加和删除都要考虑更新尾指针。
206. 反转链表(两种递归)
class Solution {
public ListNode reverse(ListNode head, ListNode pre) {
if(head == null) {
return pre;
}
ListNode tmp = head.next;
head.next = pre;
return reverse(tmp, head);
}
public ListNode reverseList(ListNode head) {
return reverse(head, null);
}
}
(待更新。。)