返回顶部

【算法题】LeetCode刷题(八)

数据结构和算法是编程路上永远无法避开的两个核心知识点,本系列【算法题】旨在记录刷题过程中的一些心得体会,将会挑出LeetCode等最具代表性的题目进行解析,题解基本都来自于LeetCode官网(https://leetcode-cn.com/),本文是第八篇。

1.买卖股票的最佳时机(原第121题)

给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。
如果你最多只允许完成一笔交易(即买入和卖出一支股票一次),设计一个算法来计算你所能获取的最大利润。
注意:你不能在买入股票前卖出股票。

示例:

输入: [7,1,5,3,6,4]
输出: 5
解释: 在第 2 天(股票价格 = 1)的时候买入,在第 5 天(股票价格 = 6)的时候卖出,最大利润 = 6-1 = 5 。注意利润不能是 7-1 = 6, 因为卖出价格需要大于买入价格;同时,你不能在买入前卖出股票。

(1)知识点

应该算贪心算法了

(2)解题方法

方法转自:https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock/solution/121-mai-mai-gu-piao-de-zui-jia-shi-ji-by-leetcode-/

方法一:暴力法

这个题其实很容易想到这个方法,我们从每个节点开始做一次遍历,比如第i个节点买入,往后找到一个最大的,一比较,存下来,然后计算第i+1个节点开始买入的最大收益,比较返回最大值就行了。

  • 时间复杂度:O(n2),循环运行n(n−1)/2次。
  • 空间复杂度:O(1),不需要额外的空间开销。

方法二:一次遍历

事实上暴力法可以简化,我们可以换一个思想,我要往后遍历数组的时候,必然会经历一个最低点,我们就可以记录到第i个点卖出的时候的最大收益,前提是遍历的过程中把最低点记录下来,这样遍历一次就够了。

  • 时间复杂度:O(n),循环运行n次。
  • 空间复杂度:O(1),不需要额外的空间开销。

(3)伪代码

函数头:int maxProfit(int prices[])

方法一:暴力法

  • 定义一个最大收益max
  • 第一重循环(i:0->len-1)
    • 第二重循环(j:i->len-1)
      • 如果prices[j]-prices[i] > max,max=这个差值

方法二:一次遍历

  • 定义一个最大收益max
  • 定义一个最低点low
  • 第一重循环(i:0->len-1)
    • 如果low>prices[i],low=prices[i],continue
    • max=Math.max(max, prices[i]-low)

(4)代码示例

public int maxProfit(int[] prices) {
    int len = prices.length;
    if(len < 2) return 0;
    int max = 0;
    int min = prices[0];
    for(int i = 1; i < len; ++i){
        max = Math.max(max, prices[i] - min);
        min = Math.min(min, prices[i]);
    }
    return max;
}


2.只出现一次的数字2(原第137题)

给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现了三次。找出那个只出现了一次的元素。
说明:
你的算法应该具有线性时间复杂度。 你可以不使用额外空间来实现吗?

示例:

输入: [2,2,3,2]
输出: 3

(1)知识点

位运算

(2)解题方法

方法转自:https://leetcode-cn.com/problems/single-number-ii/solution/zhi-chu-xian-yi-ci-de-shu-zi-ii-by-leetcode/

方法:位运算

在《只出现一次的数字》这个题中,我们发现用异或运算可以轻松解决,但是这个题是三个三个的,虽然不能直接用异或,但是我们可以采取一点措施来实现我们的目标。

  • 时间复杂度:O(n),循环运行n次。
  • 空间复杂度:O(1),不需要额外的空间开销。

(3)伪代码

函数头:int singleNumber(int[] nums)

方法:位运算

  • 暂时还没看懂,先留个坑

(4)代码示例

public int singleNumber(int[] nums) {
    int ones = 0, twos = 0;
    for (int num : nums) {
        ones = ~twos & (ones ^ num);
        twos = ~ones & (twos ^ num);
    }
    return ones;
}


3.单词拆分(原第139题)

给定一个非空字符串 s 和一个包含非空单词列表的字典 wordDict,判定 s 是否可以被空格拆分为一个或多个在字典中出现的单词。
说明:
拆分时可以重复使用字典中的单词。
你可以假设字典中没有重复的单词。

示例:

输入: s = "leetcode", wordDict = ["leet", "code"]
输出: true
解释: 返回 true 因为 "leetcode" 可以被拆分成 "leet code"。

(1)知识点

动态规划

(2)解题方法

方法转自:https://leetcode-cn.com/problems/single-number-ii/solution/zhi-chu-xian-yi-ci-de-shu-zi-ii-by-leetcode/
(官方解释太啰嗦了,看了一个稍微容易理解的)

方法:动态规划

  • 时间复杂度:O(n2)。
  • 空间复杂度:O(1)。

(3)伪代码

函数头:boolean wordBreak(String s, List wordDict)

方法:动态规划

  • 将wordDict转成HashSet
  • 定义一个数组boolean[] dp,长度为s.length()+1,dp[0]=true
  • 第一重循环,i:0->len
    • 第二重循环,j:i+1->len
      • 如果dp[i]&&s.subString(i,j)在wordDict里面,dp[j]=true

(4)代码示例

public boolean wordBreak(String s, List<String> wordDict) {
    Set<String> wordDictSet = new HashSet(wordDict);
    int len = s.length();
    boolean[] dp = new boolean[len+1];
    dp[0] = true;
    for(int i = 1; i <= len; ++i){
        for(int j = 0; j < i; ++j){
            if(dp[j] && wordDictSet.contains(s.substring(j, i))){
                dp[i] = true;
                break;
            }
        }
    }
    return dp[len];
}


4.阶乘后的零(原第172题)

给定一个整数 n,返回 n! 结果尾数中零的数量。
说明: 你算法的时间复杂度应为 O(log n) 。

示例:

输入: 5
输出: 1
解释: 5! = 120, 尾数中有 1 个零.

(1)知识点

数学

(2)解题方法

方法转自:https://leetcode-cn.com/problems/factorial-trailing-zeroes/solution/jie-cheng-hou-de-ling-by-leetcode/

方法:高效的计算因子5

这个题如果用先计算阶乘后计算零的方法不会带来线性复杂度,所以,我们需要利用数学的方法来思考。比如16!,这里面就会有三个0,16/5=3,这就是那三个0的来源,那么如果是26!呢?26/5=5,5/5=1,因此26!有六个0(注意,25里面有2个5,所以不能简单地通过计算26与1之间隔着多少个5来算,这毕竟是乘法。事实上,第二步的5/5=1就是在把25里面另一个5给找出来),同理,如果是126!,我们可以轻松得到他有25+5+1个0。

  • 时间复杂度:O(logN)。
  • 空间复杂度:O(1)。

(3)伪代码

函数头:int trailingZeroes(int n)

方法:高效的计算因子5

  • 定义一个结果count
  • 第一重循环,当n > 0
    • n = n / 5
    • count = count + n

(4)代码示例

public int trailingZeroes(int n) {
    int count = 0;
    while(n > 1){
        count += n / 5;
        n /= 5;
    }
    return count;
}


5.最大数(原第179题)

给定一组非负整数,重新排列它们的顺序使之组成一个最大的整数。

示例:

输入: [3,30,34,5,9]
输出: 9534330

(1)知识点

排序

(2)解题方法

方法转自:https://leetcode-cn.com/problems/largest-number/solution/zui-da-shu-by-leetcode/

方法:排序

首先,我们将每个整数变成字符串。然后进行排序。
如果仅按降序排序,有相同的开头数字的时候会出现问题。比方说,样例 2 按降序排序得到的数字是 95343303 ,然而交换 3 和 30 的位置可以得到正确答案 9534330 。因此,每一对数在排序的比较过程中,我们比较两种连接顺序哪一种更好。我们可以证明这样的做法是正确的:
做法很简单,定义一个组合字符的排序方法,a和b的顺序由a+b和b+a的顺序决定(这里的+是连接的意思)。有一个需要注意的情况是如果数组只包含 0 ,我们直接返回结果 0 即可。否则,我们用排好序的数组形成一个字符串并返回。

  • 时间复杂度:O(nlgn),尽管我们在比较函数中做了一些额外的工作,但是这只是一个常数因子,所以总的时间复杂度是由排序决定的。
  • 空间复杂度:O(1)。

(3)伪代码

函数头:String largestNumber(int[] nums)

方法:排序

这里最重要的是定义排序的方法(重写):(这里也可以用lambda+stream的方式)

Arrays.sort(asStrs, new Comparator<String>(){
    @Override
    public int compare(String a, String b) {
        String order1 = a + b;
        String order2 = b + a;
        return order2.compareTo(order1);
    }
});

其他的就很简单了,判断一个是否以0开始就可以了。

(4)代码示例

public String largestNumber(int[] nums) {
    int len = nums.length;
    String[] numStr = new String[len];
    for(int i = 0; i < len; ++i){
        numStr[i] = String.valueOf(nums[i]);
    }
    Arrays.sort(numStr, new Comparator<String>() {
        @Override
        public int compare(String o1, String o2) {
            String a1 = o1 + o2;
            String a2 = o2 + o1;
            return a2.compareTo(a1);
        }
    });
    
    if(numStr[0].equals("0")) return "0";
    StringBuilder sb = new StringBuilder();
    for(String str : numStr){
        sb.append(str);
    }
    return sb.toString();
}


posted @ 2020-08-15 12:34  藤原豆腐店の張さん  阅读(247)  评论(0编辑  收藏  举报