leetCode进阶算法题+解析(五十四)

递增子序列

题目:给定一个整型数组, 你的任务是找到所有该数组的递增子序列,递增子序列的长度至少是2。

示例:
输入: [4, 6, 7, 7]
输出: [[4, 6], [4, 7], [4, 6, 7], [4, 6, 7, 7], [6, 7], [6, 7, 7], [7,7], [4,7,7]]
说明:
给定数组的长度不会超过15。
数组中的整数范围是 [-100,100]。
给定数组中可能包含重复数字,相等的数字应该被视为递增的一种情况。

思路:用我做的500+以上题目的经验来说。这个题绝对会卡在超时上。毕竟暴力法还是很容易实现的。我现在的想法就是从头遍历。大于上一个值的往后追加/如果小于上一个值。那么从头开一条路。反正是有个大致的思路,我去实现下试试吧。
超时一点毛病没有。58个测试案例卡在56上我光荣。用事实的教训告诉我们不要暴力破解,还是要讲究方法的。哈哈

超时

其实这个方法在写的过程中一直有隐隐的思路。比如记忆功能啊,甚至dp啊回溯剪枝啊什么的。至于这个去重的话我之前的暴力法的时候是用set把所有的结果集转成字符串记录的,效果杠杠的,当然超时就不说了。但是总体来讲一说到去重set,hash这些还是不可避免的想到。我还是继续去研究下怎么实现吧。

class Solution {
    List<Integer> temp = new ArrayList<Integer>();
    List<List<Integer>> ans = new ArrayList<List<Integer>>();

    public List<List<Integer>> findSubsequences(int[] nums) {
        dfs(0, Integer.MIN_VALUE, nums);
        return ans;
    }
    //这个last是上一个元素。应该这样比每次都获取list中最后一个元素性能要来的好吧
    public void dfs(int cur, int last, int[] nums) {
        //当前元素已经没有了,说明这条线到头了。
        if (cur == nums.length) {
            if (temp.size() >= 2) {
                ans.add(new ArrayList<Integer>(temp));
            }
            return;
        }
        if (nums[cur] >= last) {
            temp.add(nums[cur]);
            dfs(cur + 1, nums[cur], nums);
            temp.remove(temp.size() - 1);
        }
        if (nums[cur] != last) {//如果当前元素和上一个元素不相等,那么可以继续往下走。但是等于的话,再走一边就是重复了。
            dfs(cur + 1, last, nums);
        }
    }
}

这个代码是参考题解做的,我觉得自己想不出来了。反正和我第一版本的代码百分之六十逻辑上的相等的。属于都能看懂,就是自己写不出来。就好像我的js代码。反正这个题就是这样的,这个代码我觉得不难,但是巧妙的很,毕竟差不多的思路我就超时了,这个就超过百分百的人了,哎。下一题。

目标和

题目:给定一个非负整数数组,a1, a2, ..., an, 和一个目标数,S。现在你有两个符号 + 和 -。对于数组中的任意一个整数,你都可以从 + 或 -中选择一个符号添加在前面。返回可以使最终数组和为目标数 S 的所有添加符号的方法数。

示例:
输入:nums: [1, 1, 1, 1, 1], S: 3
输出:5
解释:
-1+1+1+1+1 = 3
+1-1+1+1+1 = 3
+1+1-1+1+1 = 3
+1+1+1-1+1 = 3
+1+1+1+1-1 = 3
一共有5种方法让最终目标和为3。
提示:
数组非空,且长度不会超过 20 。
初始的数组的和不会超过 1000 。
保证返回的最终结果能被 32 位整数存下。

思路:这个题思路还是很清楚的。其实简单的想就是每一个元素有+/-两个选择。其实和选/不选是一个性质。感觉可以变种为01背包问题。我是觉得用二维数组所有的可能列出来就可以了。当然了我dp渣也不是一天两天了。我觉得其实用回溯也是可以实现的。每一个元素分两种情况,从第一个开始递归。嘿嘿。甚至跟上一题有点类似。有思路,我去实现下试试。
这个题比我想的还要简单,我直接贴代码:

class Solution {
    int temp;
    int res = 0;
    public int findTargetSumWays(int[] nums, int S) {
        temp = S;
        dfs(nums, 0, 0);
        return res;
    }
    public void dfs(int[] nums,int cur,int sum){
        if(cur == nums.length) {
            if(sum == temp) res++;
            return;
        }
        dfs(nums, cur+1, sum+nums[cur]);
        dfs(nums, cur+1, sum-nums[cur]);
    }
}

感觉题生达到了巅峰,哈哈,最近做的最顺的一道题。当然了这种暴力法性能就不能看了。我仍然觉得这个题是可以用dp来实现的。毕竟动态规划就是记录中间过程的递归。不过具体怎么实现没思路。我打算去看题解了。

(来源于题解)思路:公式推导+一维01背包求方案数。设构成正数的集合为sum(正),构成负数的集合为sum('负'),则有sum(正)-sum('负')=S。 等式两边都加上sum(nums)得:2*sum(正)=S+sum(nums),当且仅当等式S+sum(nums)为偶数且sum(nums)>=S时,等号成立!于是问题求解就变成先求出sum(正),再求出数组中哪些元素组合在一起的和为sum(正)的方案数!即剩下的用01背包求方案数即可。

class Solution {
    public int findTargetSumWays(int[] nums, int S) {
        int len, sum = 0, res = 0;
        if (nums == null || (len = nums.length) == 0)
            return res;
        for (int i = 0; i < len; i++)
            sum += nums[i];
        if (sum < S || ((sum += S) & 1) == 1) // 若是奇数或者数组元素总和小于S,则无解
            return res;
        sum >>= 1;
        int[] dp = new int[sum + 1];
        dp[0] = 1; // 初始状态值:若刚好取到sum,则方案数加1
        for (int i = 0; i < len; i++) {
            for (int j = sum; j >= nums[i]; j--) // 01背包,一维数组实现,逆序枚举,保证每个元素都被只用一次
                dp[j] += dp[j - nums[i]];
        }
        return dp[sum];
    }
}

简单来说感觉看懂dp思路比做这个题本身都难了。哎,这个题解是我觉得比较好理解的一个了。反正我就看看,理解理解就完了,现在我对dp有点绝望了。反正也走不了算法岗,所以简单的会点得了。下一题。

提莫攻击

题目:在《英雄联盟》的世界中,有一个叫 “提莫” 的英雄,他的攻击可以让敌方英雄艾希(编者注:寒冰射手)进入中毒状态。现在,给出提莫对艾希的攻击时间序列和提莫攻击的中毒持续时间,你需要输出艾希的中毒状态总时长。你可以认为提莫在给定的时间点进行攻击,并立即使艾希处于中毒状态。

示例1:
输入: [1,4], 2
输出: 4
原因: 第 1 秒初,提莫开始对艾希进行攻击并使其立即中毒。中毒状态会维持 2 秒钟,直到第 2 秒末结束。
第 4 秒初,提莫再次攻击艾希,使得艾希获得另外 2 秒中毒时间。
所以最终输出 4 秒。
示例2:
输入: [1,2], 2
输出: 3
原因: 第 1 秒初,提莫开始对艾希进行攻击并使其立即中毒。中毒状态会维持 2 秒钟,直到第 2 秒末结束。
但是第 2 秒初,提莫再次攻击了已经处于中毒状态的艾希。
由于中毒状态不可叠加,提莫在第 2 秒初的这次攻击会在第 3 秒末结束。
所以最终输出 3 。
提示:
你可以假定时间序列数组的总长度不超过 10000。
你可以假定提莫攻击时间序列中的数字和提莫攻击的中毒持续时间都是非负整数,并且不超过 10,000,000。

思路:这个题怎么说呢,可爱的很。当然了,一小部分是因为我年轻的视乎有一段时间很喜欢玩提莫。一大部分原因是因为这个题目比较简单。哈哈。给定的数组是攻击的时间。后面的数值是中毒的时间。这个题比较麻烦的就是中毒重复时间了吧。反正我觉得实现的方法是多种多样的。最最简单的就是创建一个数组用来记录。中毒了值+1.最后统计所有非0的数值的个数。不过这个需要额外空间的。但是这个题没有任何时间空间的限制。当然了,用set也行,中毒时候的秒数扔到set里。利用set的不重复性最后算set的长度、我去实现下试试。
呵,提莫害我!这个题果断的超时了。附上超时代码:

超时代码

哎,我是觉得思路没问题。我想想这个怎么减少计算吧。
想出了一种不用set每个值,直接计算的方法。附上思路简图:
直接计算中毒秒数

做是做出来的,通过也通过了,性能也很哇塞。就他妈这个题的测试案例让爷气笑了。时间居然还能从0开始?以为是下标咋的。两个demo都是1开始,我就自动以为是从1开始的了,哎。附上代码:

class Solution {
    public int findPoisonedDuration(int[] timeSeries, int duration) {
        int sum = 0;
        int last = -1;//当前最后一个中毒的秒数
        for(int i:timeSeries) {
            //如果等于说明也重合了。
            sum += last<i?duration:i+duration-1-last;
            //最后一个中毒的格子,这个不变
            last = i+duration-1;
        }
        return sum;  
    }
}

这个题挺简单的。结合我上面的图就不多说什么了。直接下一题。

非重叠矩形中的随机点

题目:给定一个非重叠轴对齐矩形的列表 rects,写一个函数 pick 随机均匀地选取矩形覆盖的空间中的整数点。

提示:
整数点是具有整数坐标的点。
矩形周边上的点包含在矩形覆盖的空间中。
第 i 个矩形 rects [i] = [x1,y1,x2,y2],其中 [x1,y1] 是左下角的整数坐标,[x2,y2] 是右上角的整数坐标。
每个矩形的长度和宽度不超过 2000。
1 <= rects.length <= 100
pick 以整数坐标数组 [p_x, p_y] 的形式返回一个点。
pick 最多被调用10000次。
示例 1:
输入:
["Solution","pick","pick","pick"]
[[[[1,1,5,5]]],[],[],[]]
输出:
[null,[4,1],[4,1],[3,3]]
示例 2:
输入:
["Solution","pick","pick","pick","pick","pick"]
[[[[-2,-2,-1,-1],[1,0,3,0]]],[],[],[],[],[]]
输出:
[null,[-1,-2],[2,0],[-2,-1],[3,0],[-2,-2]]
输入语法的说明:
输入是两个列表:调用的子例程及其参数。Solution 的构造函数有一个参数,即矩形数组 rects。pick 没有参数。参数总是用列表包装的,即使没有也是如此。

思路:这个题题目很明确。我觉得所谓的随机就是横纵坐标随机。现在的想法是横纵坐标都有范围。首先第一个random在第几个矩阵。其次random矩阵的横纵坐标。总感觉可能我想的太简单了,我先去试试看看吧。
我上面的想法确实有点问题。落在哪个矩形不应该是一样几率的,因为矩形大小不同。这个题应该是算出所有的可能的点数,随机一个结果,然后去反找这个点:

class Solution {

    int[][] rects;
    List<Integer> psum = new ArrayList<>();
    int tot = 0;
    Random rand = new Random();

    public Solution(int[][] rects) {
        this.rects = rects;
        for (int[] x : rects){
            tot += (x[2] - x[0] + 1) * (x[3] - x[1] + 1);
            psum.add(tot);
        }
    }

    public int[] pick() {
        int targ = rand.nextInt(tot);

        int lo = 0;
        int hi = rects.length - 1;
        while (lo != hi) {
            int mid = (lo + hi) / 2;
            if (targ >= psum.get(mid)) lo = mid + 1;
            else hi = mid;
        }

        int[] x = rects[lo];
        int width = x[2] - x[0] + 1;
        int height = x[3] - x[1] + 1;
        int base = psum.get(lo) - width * height;
        return new int[]{x[0] + (targ - base) % width, x[1] + (targ - base) / width};
    }
}
/**
 * Your Solution object will be instantiated and called as such:
 * Solution obj = new Solution(rects);
 * int[] param_1 = obj.pick();
 */

我是不太喜欢这个题。难又不难,做又不容易做,哎。下一题。

对角线遍历

题目:给定一个含有 M x N 个元素的矩阵(M 行,N 列),请以对角线遍历的顺序返回这个矩阵中的所有元素,对角线遍历如下图所示。

示例:
输入:
[
[ 1, 2, 3 ],
[ 4, 5, 6 ],
[ 7, 8, 9 ]
]
输出: [1,2,4,7,5,3,6,8,9]

解释

思路:这个题我感觉做过类似的,就是过年那段时间刷的,二维数组的旋转,翻转啥的差不多。我感觉技巧也就是看成二维坐标系。横纵坐标的更改吧。我去试试代码。

简图(因为给定的是正方形所以我画个长方形更容易理解)

分两种情况:一种是往上走呢,一种是往下走。比如是d[i][j]。往上走是i-1,j+1. 往下走是i+1.j-1.当然走到边边是要特殊处理的。反正我觉得比较好实现,我去试试。
啧啧,完美的一次过,性能在能接受的范围,起码我思路没错。我先附上代码:

class Solution {
    public int[] findDiagonalOrder(int[][] matrix) {
        if(matrix==null || matrix.length==0 || matrix[0].length==0) return new int[0];
        int m = matrix.length;
        int n = matrix[0].length;
        int[] res = new int[m*n];
        int idx = 0;
        int tempM = 0;
        int tempN = 0;
        int flag = 0;//0是向上走,1是向下走
        while(idx<m*n){//元素没填满
            res[idx] = matrix[tempM][tempN];
            if(flag == 0){//向上走
                //只要判断是不是到边上了
                if(tempM == 0 || tempN == n-1){
                    //无法往上走,但是可以往右边走一步然后往下
                    if(tempM == 0 && tempN !=n-1){
                        tempN++;
                    }else{//无法往上走,但是可以往下走一步然后往下
                        tempM++;
                    }
                    flag = 1;//只要到边上就要改变方向
                }else{
                    //每到边上正常往斜上方走。
                    tempM--;
                    tempN++;
                }
            }else{//向下走
                //到没到边上
                if(tempM == m-1 || tempN == 0){
                    if(tempN ==0 && tempM != m-1){
                        tempM++;
                    }else{
                        tempN++;
                    }
                    flag = 0;
                }else{
                    tempM++;
                    tempN--;
                }
            }
            idx++;
        }
        return res;
    }
}

这两天做的题太让我有信心了啊,是不是简单的题目都聚在一起了,几乎没有卡顿,都差不多一次过,哈哈。
这个题其实只要分析清楚了,并且把可能遇到的情况都列出来,几乎就没难度。当然了我觉得我这个肯定是写鸡肋了,我去看看性能排行第一的代码:
思路大同小异,实现略有不同,我直接贴上:

class Solution {
    int[] res;
    int k = 0;
    public int[] findDiagonalOrder(int[][] matrix) {
        if(matrix.length == 0 || matrix[0].length == 0) return new int[0];
        res = new int[matrix.length * matrix[0].length];
        if(matrix[0].length == 1) {
            for(int i=0; i<matrix.length; i++) {
                res[i] = matrix[i][0];
            }
            return res;
        }
        if(matrix.length == 1) return matrix[0];
        dfs(matrix, 0, 0, true);
        return res;
    }

    private boolean dfs(int[][] matrix, int i, int j, boolean flag) {
        if(i < 0 || j < 0 || i >= matrix.length || j >= matrix[0].length) return false;
        res[k++] = matrix[i][j];
        if(i+1 == matrix.length && j+1 == matrix[0].length) return true;
        if(flag) {
            if(!dfs(matrix, i-1, j+1, flag)) return dfs(matrix, i, j+1, !flag);
        } else {
            if(!dfs(matrix, i+1, j-1, flag)) return dfs(matrix, i+1, j, !flag);
        }
        return true;
    }
}

下一题了啊。

下一个更大的元素2

题目:给定一个循环数组(最后一个元素的下一个元素是数组的第一个元素),输出每个元素的下一个更大元素。数字 x 的下一个更大的元素是按数组遍历顺序,这个数字之后的第一个比它更大的数,这意味着你应该循环地搜索它的下一个更大的数。如果不存在,则输出 -1。

示例 1:
输入: [1,2,1]
输出: [2,-1,2]
解释: 第一个 1 的下一个更大的数是 2;
数字 2 找不到下一个更大的数;
第二个 1 的下一个最大的数需要循环搜索,结果也是 2。
注意: 输入数组的长度不会超过 10000。

思路:感觉这个题唯一可能卡住的也就是性能了。这个题单纯的n方遍历肯定是没问题,但是没必要。这个题我暂时的思路肯定是要记录的,但是具体记录什么,怎么记录我先想想。
这里用了栈。毕竟元素之间要么大于,要么小于等于。如果是大于的话,那么当前元素的下一个更大元素直接就知道了。但是如果下一个值小于当前元素,那么存入栈中,继续往下判断。这样能确定栈中的元素肯定是从大到小的。当然了每到一个下一个元素较大的时候要循环一次栈中的元素。看能出几个出几个。大概思路就是这样,代码如下:

class Solution {
    public int[] nextGreaterElements(int[] nums) {
        int maxIdx = 0;
        // 找到最大值的下标
        for (int i = 0; i < nums.length; i++) 
            maxIdx = (nums[maxIdx] > nums[i] ? maxIdx : i);
        LinkedList<Integer> stack = new LinkedList<>();
        for (int i = nums.length - 1; i >= 0; i--) {
            int idx = (i + maxIdx + 1) % nums.length; // 从最大的值开始绕一圈
            int idxNum = nums[idx];
            while (!stack.isEmpty() && stack.getLast() <= idxNum) 
                stack.removeLast();
            nums[idx] = (stack.isEmpty() ? -1 : stack.getLast());
            stack.add(idxNum);
        }
        return nums;
    }
}

本来我是循环了两遍这个数组,不过看了别人的评论所以直接从最大值开始绕圈遍历就好了。这个题只要思路清晰了也不算难。另外也是看了评论才知道这个题暴力解法居然不超时!反正就这样了。
这篇笔记就记到这里,如果稍微帮到你了记得点个喜欢点个关注,也祝大家工作顺顺利利!生活愉快!

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,639评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,277评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,221评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,474评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,570评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,816评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,957评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,718评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,176评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,511评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,646评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,322评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,934评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,755评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,987评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,358评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,514评论 2 348