【算法题】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)解题方法
方法一:暴力法
这个题其实很容易想到这个方法,我们从每个节点开始做一次遍历,比如第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=这个差值
- 第二重循环(j:i->len-1)
方法二:一次遍历
- 定义一个最大收益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)解题方法
方法:位运算
在《只出现一次的数字》这个题中,我们发现用异或运算可以轻松解决,但是这个题是三个三个的,虽然不能直接用异或,但是我们可以采取一点措施来实现我们的目标。
- 时间复杂度: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转成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
- 第二重循环,j:i+1->len
(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)解题方法
方法:高效的计算因子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();
}