LeetCode 445, 340, 312, 309, 300

LeetCode 445 Add Two Numbers II

链接:https://leetcode.com/problems/add-two-numbers-ii/

方法:双指针

时间复杂度:O(n)
想法:这个其实就是双指针的那种技巧性非常强的题...可能都不是很容易应用到其他题目上。但不管怎么样,这道题,想法是别管怎么样,先把各个对应的节点加起来,哪怕加起来的数超过9,比方说l1 = [7,2,4,3], l2 = [5,6,4],别管三七二十一,先加出一个[7,7,10,7]。这样加完之后得到了一个新链表,然后开始处理这个链表。先用一个指针p,然后用一个指针q从p.next开始,扫过所有值为9的地方,那如果扫完一片9,最后一个数字超过了9,我们就知道,这个地方应该进位,进位的结果就是前面所有的9全变0,最前面也就是p那个地方加1,实现上就是p一路挪过来,挪到q的位置。如果扫过一片9之后q到达的位置比9小,那直接把p挪到这里来,说明这一段没有进位,不用处理。
代码:

class Solution {
    public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
        int n1 = getLength(l1), n2 = getLength(l2);
        int s = Math.max(n1, n2);
        ListNode dummy = new ListNode(0);
        ListNode p = dummy;
        
        while (s > 0) {
            p.next = new ListNode(0);
            p = p.next;
            
            if (s <= n1) {
                p.val += l1.val;
                l1 = l1.next;
            }
            if (s <= n2) {
                p.val += l2.val;
                l2 = l2.next;
            }
            
            s--;
        }
        
        p = dummy;
        while (p != null) {
            ListNode q = p.next;
            while (q != null && q.val == 9) {
                q = q.next;
            }
            
            if (q != null && q.val > 9) {
                while (p != q) {
                    p.val++;
                    p = p.next;
                    p.val -= 10;
                }
            }
            else {
                p = q;
            }
        }
        
        if (dummy.val > 0) {
            return dummy;
        }
        
        return dummy.next;
    }
    
    private int getLength(ListNode l) {
        int res = 0;
        while (l != null) {
            res++;
            l = l.next;
        }
        return res;
    }
}

LeetCode 340 Longest Substring with At Most K Distinct Characters

链接:https://leetcode.com/problems/longest-substring-with-at-most-k-distinct-characters/

方法:滑动窗口

时间复杂度:O(n)
想法:这个题倒是比较基本,就是滑动窗口的模板题。外面一个for,里面一个while。唯一要注意的就是滑动j的条件。在我的滑动窗口的模板中,i指的是滑动窗口的左端点,j指的是右端点右边一个的那个不合法的位置,所以在while里面j滑到这个地方就停。因为j指向的是第一个不合法的位置,因此j的判断条件需要是while (j < n && (cnt < k || (cnt == k && map[s.charAt(j)] > 0)))
代码:

class Solution {
    public int lengthOfLongestSubstringKDistinct(String s, int k) {
        int res = 0, n = s.length(), i = 0, j = 0, cnt = 0;
        int[] map = new int[256];
        
        for (; i < n; i++) {
            while (j < n && (cnt < k || (cnt == k && map[s.charAt(j)] > 0))) {
                map[s.charAt(j)]++;
                if (map[s.charAt(j)] == 1) {
                    cnt++;
                }
                j++;
            }
            
            res = Math.max(res, j - i);
            
            map[s.charAt(i)]--;
            if (map[s.charAt(i)] == 0) {
                cnt--;
            }
        }
        
        return res;
    }
}

LeetCode 312 Burst Balloons

链接:https://leetcode.com/problems/burst-balloons/

方法:DP

时间复杂度:O(n3)
想法:经典区间型DP。用dp[i][j]代表打掉(i,j)开区间内的所有气球拿到的最大钱数(把i-j中间的气球全部打掉,只剩下i和j)。因为题目当中说原数组的两边相当于有两个1在旁边,所以干脆听他的直接改数组,就在前面后面各放一个1,这样在循环的时候就不用写各种if了。然后dp[i][j],我们可以想象中间有个k,在分割这个区间,把区间(i, j)分成(i, k), k, (k, j)三部分,然后打出dp[i][j]的最后一步是打气球k,在此之前把左右两个区间内的气球全打没了,所以最后一步的收益是A[i] * A[k] * A[j]。这样的话我们通过枚举k就能得到dp[i][j]的所有可能的值,当然也能得到最大值。那么dp[i][j] = Math.max(dp[i][j], dp[i][k] + dp[k][j] + A[i] * A[k] * A[j])。
代码:

class Solution {
    public int maxCoins(int[] nums) {
        int n = nums.length;
        n += 2;
        int[] A = new int[n];
        A[0] = 1;
        A[n - 1] = 1;
        
        for (int i = 1; i < n - 1; i++) {
            A[i] = nums[i - 1];
        }
        
        int[][] dp = new int[n][n];
        
        for (int len = 3; len <= n; len++) {
            for (int i = 0; i <= n - len; i++) {
                int j = i + len - 1;
                for (int k = i + 1; k < j; k++) {
                    dp[i][j] = Math.max(dp[i][j], dp[i][k] + dp[k][j] + A[i] * A[k] * A[j]);
                }
            }
        }
        
        return dp[0][n - 1];
    }
}

LeetCode 309 Best Time to Buy and Sell Stock with Cooldown

链接:https://leetcode.com/problems/best-time-to-buy-and-sell-stock-with-cooldown/

方法:DP

时间复杂度:O(n)
想法:买卖股票大家族的其中一道题,有机会可以把这个系列全写一遍。这个题主要是分析状态转化。其中有一些空间复杂度上的简化可以做,但写一个O(n)时间复杂度的算法还是并不难。先放一种最直观的想法,就是sell、buy和cooldown数组的第i个元素分别表示在第i天之前,最后一次操作是卖、买和不动的最大收益。那么sell[i] = Math.max(sell[i - 1], buy[i - 1] + price),buy[i] = Math.max(buy[i - 1], cooldown[i - 1] - price),cooldown[i] = Math.max(sell[i - 1], Math.max(buy[i - 1], cooldown[i - 1]))。
代码:

class Solution {
    public int maxProfit(int[] prices) {
        int n = prices.length;
        int[] sell = new int[n];
        int[] buy = new int[n];
        int[] cooldown = new int[n];
        buy[0] = -prices[0];
        
        for (int i = 1; i < n; i++) {
            int price = prices[i];
            sell[i] = Math.max(sell[i - 1], buy[i - 1] + price);
            buy[i] = Math.max(buy[i - 1], cooldown[i - 1] - price);
            cooldown[i] = Math.max(sell[i - 1], Math.max(buy[i - 1], cooldown[i - 1]));
        }
        
        return Math.max(sell[n - 1], Math.max(buy[n - 1], cooldown[n - 1]));
    }
}

继续观察可知,cooldown[i] = sell[i-1],因为cooldown[i]代表在第i天以及它之前,最后一次操作为啥也不干的最大收益,既然最后一次操作是啥也不干,那其实就是说这次操作之前的那次操作是卖股票。那卖股票最晚发生在i-1处,所以cooldown[i] = sell[i-1]。带入之后可以把三个数组化简为两个数组。

class Solution {
    public int maxProfit(int[] prices) {
        int n = prices.length;
        int[] sell = new int[n + 1];
        int[] buy = new int[n + 1];
        sell[0] = 0;
        buy[0] = 0;
        sell[1] = 0;
        buy[1] = -prices[0];
        
        for (int i = 2; i <= n; i++) {
            sell[i] = Math.max(sell[i - 1], buy[i - 1] + prices[i - 1]);
            buy[i] = Math.max(buy[i - 1], sell[i - 2] - prices[i - 1]);
        }
        
        return Math.max(sell[n], buy[n]);
    }
}

然后继续观察发现数组当中i这个下标的值仅与i-1和i-2两个状态有关,这类问题全都可以将空间复杂度再往下压一维,就是滚动数组。

class Solution {
    public int maxProfit(int[] prices) {
        int preBuy = 0, preSell = 0, sell = 0, buy = Integer.MIN_VALUE;
        
        for (int price : prices) {
            preBuy = buy;
            buy = Math.max(preSell - price, preBuy);
            preSell = sell;
            sell = Math.max(preSell, preBuy + price);
        }
        
        return Math.max(sell, buy);
    }
}

LeetCode 300 Longest Increasing Subsequence

链接:https://leetcode.com/problems/longest-increasing-subsequence/

方法:DP + 贪心 + 二分

时间复杂度:O(nlogn)
想法:经典题目,简称LIS,后续在一些hard的题目当中又作为解题的关键步骤。我之前想出了那个n平方的DP做法,这种nlogn的没想出来,是看的别人做的。这个DP的巧妙之处在于,用一个列表dp,这里面第i个元素代表是:在所有长度为i的LIS中,结尾的元素最小的那个LIS,它的结尾元素的值是多少。我们为什么只关心结尾元素最小的LIS呢?因为比方说我有两个长度为2的LIS,分别是[1,2]和[3,4],那么当求长度为3的LIS的时候,所有元素都更容易放在[1,2]后面产生长度为3的LIS。比方有一个元素是3,3可以跟[1,2]形成[1,2,3],但就没法放到第二个序列。所以这里使用贪心,贪结尾元素最小。
然后根据上述的DP规则,可以发现DP列表是一个单调递增的列表。当我们遍历到一个原数组的元素的时候,如果它可以跟之前的一些元素形成最长的increasing subsequence的话,它就应该去找DP列表中第一个>=它的元素,因为在这个元素的左边都是严格小于它的,那它组成的LIS就会是最后一个严格小于它的这个地方代表的长度+1,也就是第一个>=它的元素代表的长度。这个时候更新这个地方结尾的最小元素为当前元素。当然如果DP列表中根本找不到>=它的,那说明最后一个小于它的就是DP列表的最后一个元素,他就跟这个长度的sequence合起来形成LIS。
代码:

class Solution {
    public int lengthOfLIS(int[] nums) {
        List<Integer> dp = new ArrayList<>();
        
        for (int num : nums) {
            int index = firstGreaterOrEqual(dp, num);
            if (index == -1) {
                dp.add(num);
            }
            else {
                dp.set(index, num);
            }
        }
        
        return dp.size();
    }
    
    private int firstGreaterOrEqual(List<Integer> dp, int target) {
        if (dp.size() == 0) {
            return -1;
        }
        
        int left = 0, right = dp.size() - 1;
        while (left + 1 < right) {
            int mid = left + (right - left) / 2;
            int cur = dp.get(mid);
            if (cur >= target) {
                right = mid;
            }
            else {
                left = mid;
            }
        }
        
        if (dp.get(left) >= target) {
            return left;
        }
        if (dp.get(right) >= target) {
            return right;
        }
        
        return -1;
    }
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,723评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,003评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,512评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,825评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,874评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,841评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,812评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,582评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,033评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,309评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,450评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,158评论 5 341
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,789评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,409评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,609评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,440评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,357评论 2 352

推荐阅读更多精彩内容