Bootstrap

【剑指Offer】个人学习笔记_57_和为s的 两个数字&连续正数序列

刷题日期:下午3:09 2021年5月20日星期四

个人刷题记录,代码收集,来源皆为leetcode

经过多方讨论和请教,现在打算往Java方向发力

主要答题语言为Java

题目:

剑指 Offer 57. 和为s的两个数字

难度简单101

输入一个递增排序的数组和一个数字s,在数组中查找两个数,使得它们的和正好是s。如果有多对数字的和等于s,则输出任意一对即可。

示例 1:

输入:nums = [2,7,11,15], target = 9
输出:[2,7] 或者 [7,2]

示例 2:

输入:nums = [10,26,30,31,47,60], target = 40
输出:[10,30] 或者 [30,10]

限制:

  • 1 <= nums.length <= 10^5
  • 1 <= nums[i] <= 10^6
题目分析

没有限制效率,先用最直观的解法,挨个匹配。

如果元素本身就大于目标,直接跳出。

初始解答:

暴力循环

class Solution {
    public int[] twoSum(int[] nums, int target) {
        if(nums.length <= 2) return nums; //边界条件
        int[] res = new int[2];
        for(int i = 0; i < nums.length; i++) {
            if(nums[i] >= target) break;
            for(int j = i + 1; j < nums.length; j++) {
                if(nums[j] >= target) break;
                if((nums[i] + nums[j]) == target) {
                    res[0] = nums[i];
                    res[1] = nums[j];
                }
            }
        }
        return res;
    }
}

可以跑的通,但是最后会超出限制。

添加备注 最后执行的输入:

[5,7,9,17,40,41,70,75,76,80,112,120,127,139,140,145,192,219,219,223,231,239,269,271,273,277,281,293,297,314,320,339,351,379,414,419,420,449,470,475,479,485,539,541,556,559,564,568,571,577,585,597,603,617,623,631,638,639,646,652,657,658,666,681,686,687,687,702,714,726,729,739,757,768,768,801,827,836,838,849,852,857,861,870,884,889,908,916,932,952,953,961,982,996,1015,1022,1044,1052,1058,1061,1062,1062,1076,1078,1085,1086,1088,1095,1103,1109,1121,1127,1139,1158,1159,1165,1177,1191,1194,1197,1206,1250,1253,1255,1257,1273,1281,1284,1284,1294,1308,1312,1324,1334,1348,1351,1371,1371,1375,1391,1411,1421,1427,1428,1433,1439,1446,1446,1452,1464,1478,1479,1490,1494,1497,1498,1522,1542,1544,1552,1560,1565,1565,1575,1600,1603,1633,1643,1661,1689,1713,1716,1721,1759,1762,1762,1765,1769,1770,1774,1775,1778,1779,1807,1808,1811,1824,1837,1854,1860,1867,1868,1868,1914,1929,1938,1945,1957,1957,1959,1968,1987,1989,1990,2006,2010,2034,2055,2096,2119,2129,2131,2133,2163,2177,2182,2185,2188,2194,2202,2206,2 1261980

查看全部

class Solution {
    public int[] twoSum(int[] nums, int target) {
        if(nums.length <= 2) return nums; //边界条件
        int[] res = new int[2];
        res[0] = 0;
        res[1] = 0;
        int i = 0, j = 1;
        while(res[0] == 0 && res[1] == 0) {
            while(res[0] == 0 && res[1] == 0) {
                if((nums[i] + nums[j]) == target) {
                    res[0] = nums[i];
                    res[1] = nums[j++];
                }
            }
            i++;
        }
        return res;
    }
}

还是不好使,放弃了,参考方法一

class Solution {
    public int[] twoSum(int[] nums, int target) {
        int l = 0, r = nums.length - 1; //左右指针
        while(l < r) { //未相交
            if ((nums[l] + nums[r]) == target) return new int[] {nums[l], nums[r]};
            if ((nums[l] + nums[r]) > target) r--;
            if ((nums[l] + nums[r]) < target) l++;
        }
        return null;
    }
}

够简单够直观。执行结果:通过

显示详情 添加备注

执行用时:3 ms, 在所有 Java 提交中击败了31.56%的用户

内存消耗:55.3 MB, 在所有 Java 提交中击败了59.01%的用户

哈希set方法,先找一个然后找剩下的另一半。

class Solution {
 public int[] twoSum(int[] nums, int target) {
        Set<Integer> set = new HashSet<>(); //哈希集,.contains方法
        for (int num : nums) {//遍历元素
            if (!set.contains(target - num))
                set.add(num);
            else 
                return new int[]{num, target - num};//很巧妙,就是多着了一段。
        }
        return new int[]{};
    }
}

执行结果:通过

显示详情 添加备注

执行用时:54 ms, 在所有 Java 提交中击败了12.04%的用户

内存消耗:55.6 MB, 在所有 Java 提交中击败了24.93%的用户

学习他人:

方法一:

zhankqL2 2021-03-13

public int[] twoSum(int[] nums, int target) {
        int l = 0,r = nums.length - 1;
        while(l < r){
            if((nums[l] + nums[r]) == target) return new int[] {nums[l],nums[r]};
            if((nums[l] + nums[r]) > target) r--;
            if((nums[l] + nums[r]) < target) l++;
        }
        return null;
    }

方法二:

罗隽 6 天前

class Solution {
    public int[] twoSum(int[] nums, int target) {
        int i=0,j=nums.length-1;
        int a[] = new int[2];
        while(i<j){
            int sum  = nums[i]+nums[j];
            if(sum>target){
                j--;
            }else if(sum<target){
                i++;
            }else{
                a[0] = nums[i];
                a[1] = nums[j];
                break;
            }
        }
        return a;
    }
}

方法三:

宝石L2 2020-02-14 Java 题解 简单明了

可以利用set来找出答案,但是题中说数组是递增的,故也可以用双指针。

    public int[] twoSum(int[] nums, int target) {
        
        int left = 0, right = nums.length - 1;

        while (left < right) {

            int cur = nums[left] + nums[right];

            if (cur == target)
                return new int[]{nums[left], nums[right]};
            else if (cur > target)
                right--;
            else 
                left++;
        }

        return new int[]{};
    }
 public int[] twoSum(int[] nums, int target) {
        
        Set<Integer> set = new HashSet<>();
        for (int num : nums) {

            if (!set.contains(target - num))
                set.add(num);
            else 
                return new int[]{num, target - num};
        }

        return new int[]{};
    }

方法四:

解题思路:

利用 HashMap 可以通过遍历数组找到数字组合,时间和空间复杂度均为O(N) ;

注意本题的 nums是 排序数组 ,因此可使用 双指针法 将空间复杂度降低至 O(1) 。

作者:jyd
链接:https://leetcode-cn.com/problems/he-wei-sde-liang-ge-shu-zi-lcof/solution/mian-shi-ti-57-he-wei-s-de-liang-ge-shu-zi-shuang-/
来源:力扣(LeetCode)

class Solution {
    public int[] twoSum(int[] nums, int target) {
        int i = 0, j = nums.length - 1;
        while(i < j) {
            int s = nums[i] + nums[j];
            if(s < target) i++;
            else if(s > target) j--;
            else return new int[] { nums[i], nums[j] };
        }
        return new int[0];
    }
}

题目:

剑指 Offer 57 - II. 和为s的连续正数序列

难度简单265

输入一个正整数 target ,输出所有和为 target 的连续正整数序列(至少含有两个数)。

序列内的数字由小到大排列,不同序列按照首个数字从小到大排列。

示例 1:

输入:target = 9
输出:[[2,3,4],[4,5]]

示例 2:

输入:target = 15
输出:[[1,2,3,4,5],[4,5,6],[7,8]]

限制:

  • 1 <= target <= 10^5
题目分析

如果target是偶数的话,还会有结果吗。

如果只找两个的结果的话,可以套用1的思路,不过需要自己定义出数组

初始解答:

class Solution {
    public int[][] findContinuousSequence(int target) {
        return new int[][]{[target/2,target/2 + 1]};
    }
}

只找两个的结果的话,直接除二再加1就行了,难在前面的结果上,这种每个段落也能用两个指针来找吗,这样找一遍不依然只能找到一种情况,而且如果遍历的话,100000的长度肯定会超时。

参考他人,看了评论区果然不是一般的简单题

xuelimin (编辑过)2020-02-23

做完829题过来的,譬如说如果有两个连续的数之和等于target,那么相差为1, (target - 1) % 2 == 0, 且数组一定是从 (target - 1) / 2开始的,数组的元素就是2个;如果是3个连续的数组,那么三个数之间相差为1、2,(target - 1 - 2) % 3 == 0,且数组一定是从 (target - 1 - 2) / 3开始的,数组元素是3个,依次类推,但是注意target必须是大于0的数,且res需要倒序。

学习K神的滑动窗口

class Solution {
    public int[][] findContinuousSequence(int target) {
        int i = 1, j = 2, s = 3;
        List<int[]> res = new ArrayList<>(); //存放结果,存储数组的表
        while(i < j) { //和上一题一样的开局
            if(s == target) {
                int[] ans = new int[j - i + 1]; //灵活建立数组
                for(int k = i; k <= j; k++) ans[k - i] = k;
                res.add(ans); //表的方便之处就体现出来了
            }
            if(s >= target) { //左端点后移
                s -= i;
                i++;
            } else { //右端点右移
                j++;
                s += j;
            }
        }
        return res.toArray(new int[0][0]); //转换二维标准写法,前面的0必须写,后面的可有可无
    }
}

足够清晰了 执行结果:通过

显示详情 添加备注

执行用时:3 ms, 在所有 Java 提交中击败了78.83%的用户

内存消耗:36.5 MB, 在所有 Java 提交中击败了63.24%的用户

学习他人:

方法一:

mata川L5 (编辑过)2020-03-06 这tm也是简单题,👴佛了

看了眼评论区,都是带数学家,👴想不到数学方法,直接暴力滑动窗口就完事了。

class Solution {
    public int[][] findContinuousSequence(int target) {
        List<int[]> list = new ArrayList<>();

        //🧠里要有一个区间的概念,这里的区间是(1, 2, 3, ..., target - 1)
        //套滑动窗口模板,l是窗口左边界,r是窗口右边界,窗口中的值一定是连续值。
        //当窗口中数字和小于target时,r右移; 大于target时,l右移; 等于target时就获得了一个解
        for (int l = 1, r = 1, sum = 0; r < target; r++) {
            sum += r;
            while (sum > target) {
                sum -= l++;
            }
            if (sum == target) {
                int[] temp = new int[r - l + 1];
                for (int i = 0; i < temp.length; i++) {
                    temp[i] = l + i;
                }
                list.add(temp);
            }
        }

        int[][] res = new int[list.size()][];
        for (int i = 0; i < res.length; i++) {
            res[i] = list.get(i);
        }
        return res;
    }
}

方法二:

🔥 2 天前

class Solution {
    public int[][] findContinuousSequence(int target) {
        if(target == 1 || target == 2){
            return null;
        }
        int i=1; // 左指针
        int j=2; // 右指针
        int sum = i+j;
        List<int[]> res = new ArrayList<>();
        while(j<=(target+1)/2){
            if(sum == target){
                int[] sub = addTarget(i,j);
                res.add(sub);
                j++; // 继续向右移动搜索;
                sum+=j;
            }else if(sum>target){
                sum-=i;
                i++;
            }else{
                j++;
                sum+=j;
            }
        }
        return res.toArray(new int[res.size()][]);
    }

    private int[] addTarget(int i,int j){
        int[] sub = new int[j-i+1];
        int index = 0;
        for(;i<=j;i++){
            sub[index++] = i;
        }
        return sub;
    }
}

方法三:

林距离 3 天前

数学方法 但是我看官方题解好像可以根据求二元一次方程的方式来将内循环去掉 也就是用O(1)的复杂度求得j 那样时间复杂度直接小了一个数量级 但我没看懂 就还是用自己的想法做的

class Solution {
    public int[][] findContinuousSequence(int target) {
        List<int[]> list = new ArrayList<>();
        for (int i = 1; i < target; i++) {
            for (int j = (int)Math.sqrt(2 * target); j >= 2; j--) {
                if ((i + i + j - 1) * j / 2 == target) {
                    int[] nums = new int[j];
                    for (int k = 0; k < j; k++)
                        nums[k] = i + k;
                    list.add(nums);
                }
            }
        }

        return list.toArray(new int[0][]);
    }
}

方法四:

什么是滑动窗口

滑动窗口可以看成数组中框起来的一个部分。在一些数组类题目中,我们可以用滑动窗口来观察可能的候选结果。当滑动窗口从数组的左边滑到了右边,我们就可以从所有的候选结果中找到最优的结果。

作者:nettee
链接:https://leetcode-cn.com/problems/he-wei-sde-lian-xu-zheng-shu-xu-lie-lcof/solution/shi-yao-shi-hua-dong-chuang-kou-yi-ji-ru-he-yong-h/
来源:力扣(LeetCode)

public int[][] findContinuousSequence(int target) {
    int i = 1; // 滑动窗口的左边界
    int j = 1; // 滑动窗口的右边界
    int sum = 0; // 滑动窗口中数字的和
    List<int[]> res = new ArrayList<>();

    while (i <= target / 2) {
        if (sum < target) {
            // 右边界向右移动
            sum += j;
            j++;
        } else if (sum > target) {
            // 左边界向右移动
            sum -= i;
            i++;
        } else {
            // 记录结果
            int[] arr = new int[j-i];
            for (int k = i; k < j; k++) {
                arr[k-i] = k;
            }
            res.add(arr);
            // 左边界向右移动
            sum -= i;
            i++;
        }
    }

    return res.toArray(new int[res.size()][]);
}

方法五

K神 方法一:求和公式

作者:jyd
链接:https://leetcode-cn.com/problems/he-wei-sde-lian-xu-zheng-shu-xu-lie-lcof/solution/jian-zhi-offer-57-ii-he-wei-s-de-lian-xu-t85z/
来源:力扣(LeetCode)

class Solution {
    public int[][] findContinuousSequence(int target) {
        int i = 1;
        double j = 2.0;
        List<int[]> res = new ArrayList<>();
        while(i < j) {
            j = (-1 + Math.sqrt(1 + 4 * (2 * target + (long) i * i - i))) / 2;
            if(i < j && j == (int)j) {
                int[] ans = new int[(int)j - i + 1];
                for(int k = i; k <= (int)j; k++)
                    ans[k - i] = k;
                res.add(ans);
            }
            i++;
        }
        return res.toArray(new int[0][]);
    }
}

方法二:滑动窗口(双指针)
设连续正整数序列的左边界 i 和右边界 j ,则可构建滑动窗口从左向右滑动。循环中,每轮判断滑动窗口内元素和与目标值 target的大小关系,若相等则记录结果,若大于 target 则移动左边界 i (以减小窗口内的元素和),若小于 target则移动右边界 j (以增大窗口内的元素和)

class Solution {
    public int[][] findContinuousSequence(int target) {
        int i = 1, j = 2, s = 3;
        List<int[]> res = new ArrayList<>();
        while(i < j) {
            if(s == target) {
                int[] ans = new int[j - i + 1];
                for(int k = i; k <= j; k++)
                    ans[k - i] = k;
                res.add(ans);
            }
            if(s >= target) {
                s -= i;
                i++;
            } else {
                j++;
                s += j;
            }
        }
        return res.toArray(new int[0][]);
    }
}

总结

以上就是本题的内容和学习过程了,有序列表种查找和,指针的话也要会考虑从头尾的情况逼近结果。

欢迎讨论,共同进步。

;