leetcode老问题新发现(更新ing)

前言:之前用C++把代码随想录写的差不多了,近期在学Java,写着重写一遍题目,在复习算法的同时,顺便掌握一下Java的语法。 本贴打算作为一个长期贴,并且每道题只做提示,不会发详细的题解。旨在快速复习题目,看有没有考虑一些特殊情况。

704. 二分查找(优先级、闭开区间问题)

二分查找

  1. 注意到int mid = (left + right)/2中加法会溢出的问题。于是我写成了int mid = low + (high - low) >> 1; 。错误,优先级错误,因为位移操作符 >> 的优先级比加法 + 低。
  2. 注意闭、开区间的写法。判断条件,赋值都会有所不同。

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,)
  1. 除了上面两种方法,官方答案(法三)还给出不用一开始查找分界点的方法。自己思考回忆一下…
  2. leetcode官方给出的空间复杂度我不太赞同,他是开答案的空间就不算入空间复杂度。这样看的话原地处理(法一)并没有优势。
  3. 在写归并时,最后两个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. 螺旋矩阵 (循环方式,注意开闭区间)

螺旋矩阵II
螺旋矩阵

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);
    }
}

(待更新。。)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值