高效制胜题解记录

高效制胜

day01 求和问题

T1 1. 两数之和 数组 哈希表

方1:遍历

class Solution {
   
    public int[] twoSum(int[] nums, int target) {
   
        for(int i=0;i<nums.length;i++){
   
            //由于前i位已经互相匹配,j从i+1位开始不会遗漏
            for(int j=i+1;j<nums.length;j++){
   
                if(nums[i]+nums[j] == target){
   
                    return new int[]{
   i,j};
                }
            }
        }
        return new int[0]; //空整型数组
    }
}

方2:hashMap

方法 描述
containsKey() 检查 hashMap 中是否存在指定的 key 对应的映射关系。
containsValue() 检查 hashMap 中是否存在指定的 value 对应的映射关系。
get() 获取指定 key 对应对 value
class Solution {
   
    public int[] twoSum(int[] nums, int target) {
   
        //键是 nums[i] 值是i
        HashMap<Integer,Integer> map = new HashMap<>();
        for(int i=0;i<nums.length;i++){
   
            //target-nums[i] map中包含目标值
            //可以避免二次循环
            if(map.containsKey(target-nums[i])){
   
                return new int[]{
   i,map.get(target-nums[i])};
            }
            map.put(nums[i],i);
        }
        return new int[0]; 
    }
}

T2 167. 两数之和 II - 输入有序数组

非递减顺序排列 就想到了二分

返回值的顺序是一定的 不能用hashMap

方1:二分 自己想的

二分模板

int BinarySearch(int array[], int n, int value)
{
   
    int left = 0;
    int right = n - 1;
    // 如果这里是 int right = n 的话,那么下面有两处地方需要修改,以保证一一对应:
    // 1、下面循环的条件则是 while(left < right)
    // 2、循环内当 array[middle] > value 的时候,right = middle

    while (left <= right)  // 循环条件,适时而变
    {
   
        int middle = left + ((right - left) >> 1);  // 防止溢出,移位也更高效。同时,每次循环都需要更新。
        if (array[middle] > value)
            right = middle - 1;
        else if (array[middle] < value)
            left = middle + 1;
        else
            return middle;
        // 可能会有读者认为刚开始时就要判断相等,但毕竟数组中不相等的情况更多
        // 如果每次循环都判断一下是否相等,将耗费时间
    }
  
    return -1;
}

题解

class Solution {
   
    public int[] twoSum(int[] nums, int target) {
   
        int temp,res;
        for(int i=0;i<nums.length;i++){
   
            temp = target-nums[i];
            //一个数只能用一次,因为是非递减顺序排列所以有个相等的一定挨在一起(同时包含不相等但相加符合目标的情况)
            if((i+1)<nums.length && nums[i]+nums[i+1]==target) 
            return new int[]{
   i+1,i+2}; //因为是返回从1开始对下标处理
            //非相等或邻近符合目标
            //使用二分法快速查找目标值
            res = BinarySearch(nums,i,nums.length-1,temp);
            if(res != -1){
   
                return new int[]{
   i+1,res+1};
            }
        }
        return new int[0];
    }

    public int BinarySearch(int[] nums,int left,int right,int target){
   
        int mid;
        while(left <= right){
   
            mid = left + ((right-left) >>1); //右移1位 比除2快
            if(nums[mid] > target) 
                right = mid - 1;
            else if(nums[mid] < target)
                left = mid + 1;
            else
                return mid;
        }

        return -1;
    }
}

方二 : 双指针

class Solution {
   
    public int[] twoSum(int[] nums, int target) {
   
        int low = 0,high = nums.length - 1,sum;
        //双指针
        while(low<=high){
   
            sum = nums[low]+nums[high];
            if(sum == target)
                return new int[]{
   low+1,high+1};
            //因为是非递减顺序排列
            //比目标大,右指针左移 ;比目标小,左指针右移
            if(sum<target) low++;
            if(sum>target) high--;
        }
        return new int[0];
    }
}

day02 求和问题

T1 15. 三数之和

排序 + 双指针 本题的难点在于如何去除重复解

暴力的话3重循环,时空开销大 官解想到双指针的过程很精妙

二重和三重可以是并列的,由此想到了双指针

  1. 特判,对于数组长度 n,如果数组为 null或者数组长度小于 3,返回 []。
  2. 对数组进行排序。
  3. 遍历排序后数组
  • 若 nums[i]>0:因为已经排序好(小到大),所以后面不可能有三个数加和等于 0,直接返回结果。

  • 对于重复元素:跳过,避免出现重复解 如[-3,0,2,2,2,3]

  • 令左指针 L=i+1,右指针 R=n-1,当 L<R 时,执行循环:

    • 当 nums[i]+nums[L]+nums[R]=0,执行循环,判断左界和右界是否和下一位置重复,去除重复解。并同时将 L,R 移到下一位置,寻找新的解

    • 若和大于 0,说明 nums[R] 太大,R左移

    • 若和小于 0,说明 nums[L]太小,L 右移

class Solution {
   
    public List<List<Integer>> threeSum(int[] nums) {
   
        List<List<Integer>> res = new ArrayList<>();
        //数组为空或者长度小于3不会有结果
        if(nums==null && nums.length < 3) return res;
        
        //数组排序
        Arrays.sort(nums); //小到大排序
        int left,right,tmp,len = nums.length; //左右边界,三数和
        
        //遍历排序后的数组
        for(int i=0; i<len; i++){
   
            //对于小到大的数组当前指大于0,后面不可能有解了
            if(nums[i] >0) return res; 
            
            //跳过重复的数字
            if(i>0 && nums[i] == nums[i-1]) continue;

            left = i+1;
            right = len-1; //边界初始化
            while(left < right){
   
                tmp = nums[i]+nums[left]+nums[right]; //三数和
                if(tmp == 0){
   
                    List<Integer> list = new ArrayList<>();
                    list.add(nums[i]);
                    list.add(nums[left]);
                    list.add(nums[right]);

                    res.add(list); 

                    //跳过重复元素
                    while(left<right && nums[left+1]==nums[left]) left++;
                    while(left<right && nums[right-1]==nums[right]) right--;

                    //边界改变继续求值
                    left++;
                    right--;
                }else if(tmp <0){
   
                    //比0小左边界右移
                    left++;
                }else{
   
                    //比0大,右边界左移
                    right--;   
                }
            }
        }

        return res;
    }
}

T2 18. 四数之和

暴力4重循环,和三数之和一样最后两重简化成双指针 即n-2重循环+双指针

官解 排序+双指针

具体实现时,还可以进行一些剪枝操作:

  • 在确定第一个数之后,如果 nums[i]+nums[i+1]+nums[i+2]+nums[i+3] > target

    说明此时剩下的三个数无论取什么,四数之和一定大于target,因此退出第一重循环;

  • 在确定第一个数之后,如果

    nums[i]+nums[n-3]+nums[n-2]+nums[n-1] < target

    说明此时剩下的三个数无论取什么,四数之和一定小于 target因此第一重循环直接进入下一轮,枚举 nums[i+1]

  • 在确定前两个数之后,如果

    nums[i]+nums[j]+nums[j+1]+nums[j+2] > target

    说明此时剩下的两个数无论取什么,四数之和一定大于 target,因此退出第二重循环;

  • 在确定前两个数之后,如果

    nums[i]+nums[j]+nums[n-2]+nums[n-1] < target

    说明此时剩下的两个数无论取什么值,四数之和一定小target,因此第二重循环直接进入下一轮,枚举 nums[j+1]。

class Solution {
   
    public List<List<Integer>> fourSum(int[] nums, int target) {
   
        List<List<Integer>> res = new ArrayList<>();
        //特例处理
        if(nums == null || nums.length<4) return res; //空数组或者长度构不成答案返回空
        Arrays.sort(nums); //数组排序

        int left,right,len = nums.length,sum;

        for(int i=0;i<len-3;i++){
   
            //去掉重复值
            if(i>0 && nums[i]==nums[i-1]) continue;
            //确定一个数的剪枝
            if(nums[i]+nums[i+1]+nums[i+2]+nums[i+3] > target) break;
            if(nums[i]+nums[len-3]+nums[len-2]+nums[len-1] < target) continue;

            for(int j=i+1;j<len-2;j++){
   
                //去掉重复
                if(j>i+1 && nums[j]==nums[j-1]) continue;
                //确定二个数的剪枝
                if(nums[i]+nums[j]+nums[j+1]+nums[j+2] > target) break;
                if(nums[i]+nums[j]+nums[len-2]+nums[len-1] < target) continue;

                //初始化边界值
                left = j+1;
                right = len-1;

                //双指针
                while(left < right){
   
                    sum = nums[i]+nums[j]+nums[left]+nums[right];

                    if(sum == target){
   
                        res.add(Arrays.asList(nums[i],nums[j],nums[left],nums[right])); //合法解加入结果集
                        //边界重复值处理
                        while(left<right && nums[left]==nums[left+1]) left++;
                        while(left<right && nums[right]==nums[right-1]) right--;
                        //边界同时改变
                        left++;
                        right--;
                    }else if(sum <target){
   
                        //比目标小,右移左边界
                        left++;
                    }else{
   
                        //比目标大,左移右边界
                        right--;
                    }
                }
            }
        }

        return res;
    }
}

day03 斐波拉契数列

T1 509. 斐波那契数

递归 暴力

终止条件 f(0)=0,f(1)=1
递归方程 f(n) = f(n-1)+f(n-2)
class Solution {
   
    public int fib(int n) {
   
        if(n==0) return 0; //终止条件
        if(n==1) return 1;
        return fib(n-1)+fib(n-2);
    }
}

时间复杂度大

动态规划详细题解

带「备忘录」的递归算法,把一棵存在巨量冗余的递归树通过「剪枝」,改造成了一幅不存在冗余的递归图

动态规划叫做「自底向上」

方一:动态规划

状态转移方程

1 n=1,2

f(n) = f(n-1)+f(n-2) n>2

由于 F(n)只和 F(n-1) 与 F(n-2) 有关,因此可以使用「滚动数组思想」把空间复杂度优化成 O(1)

class Solution {
   
    public int fib(int n) {
   
        if(n<2) return n; //终止条件
        int p=0,q=0,r=1;
        for(int i=2 ; i<=n ; i++){
   
            //滚动数组
            p = q;
            q = r;
            r = p+q;
        }
        return r;
    }
}

方2 矩阵快速幂,方3 通项公式 数学方法

T2 70. 爬楼梯

斐波拉契数组 用动态规划

初始条件 dp[1]=1,dp[2]=2
动态转移方程 dp[n] = dp[n-1]+dp[n-2]
class Solution {
   
    public int climbStairs(int n) {
   
        if(n<2) return n;
        int a=0,b=1,r=1;
        for(int i=2;i<=n;i++){
   
            //滚动数组
            a = b;
            b = r;
            r = a+b;
        }
        return r;
    }
}

day04 动态规划

T1 53. 最大子序和

max{dp[n-1]+nums[i],nums[i]} 自己想到的

方一:动态规划

复杂度

时间复杂度:O(n)O(n),其中 nn 为 \textit{nums}nums 数组的长度。我们只需要遍历一遍数组即可求得答案。

空间复杂度:O(1)O(1)。我们只需要常数空间存放若干变量。

要求的答案是 max{dp(0) , dp(1) , dp(2) , …dp(i) }

动态转移 方程 dp[i] = max{dp[n-1]+nums[i],nums[i]}

class Solution {
   
    public int maxSubArray(int[] nums) {
   
        int res=nums[0],tmp=0;
        for(int i=0;i<nums.length;i++){
   
            tmp = tmp+nums[i] > nums[i] ? tmp+nums[i]:nums[i];
            res = Math.max(res,tmp); //dp[0]...dp[i-1]的最大值是res
        }
        return res;
    }
}

方二 :分治

官解 线段树

对于一个区间 [l,r],我们可以维护四个量:

lSum 表示 [l,r] 内以 ll 为左端点的最大子段和
rSum 表示 [l,r]内以 rr 为右端点的最大子段和
mSum 表示 [l,r]内的最大子段和
iSum 表示 [l,r]的区间和

  • 首先最好维护的是iSum,区间 [l,r]的 iSum 就等于「左子区间」的 iSum 加上「右子区间」的 iSum。

  • 对于 [l,r]的 lSum,存在两种可能,它要么等于「左子区间」的 lSum,要么等于「左子区间」的 iSum 加上「右子区间」的 lSum,二者取大。

  • 对于 [l,r]的 rSum,同理,它要么等于「右子区间」的 rSum,要么等于「右子区间」的 iSum 加上「左子区间」的 rSum,二者取大。

  • 当计算好上面的三个量之后,就很好计算 [l,r]的 mSum 了。我们可以考虑 [l,r]的 mSum 对应的区间是否跨越 mm——它可能不跨越 mm,也就是说 [l,r]的 mSum 可能是「左子区间」的 mSum 和 「右子区间」的 mSum 中的一个;它也可能跨越 mm,可能是「左子区间」的 rSum 和 「右子区间」的 lSum 求和。三者取大。

class Solution {
   
    public int maxSubArray(int[] nums
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值