842. 将数组拆分成斐波那契序列
给定一个数字字符串 S,比如 S = “123456579”,我们可以将它分成斐波那契式的序列 [123, 456, 579]。
形式上,斐波那契式序列是一个非负整数列表 F,且满足:
0 <= F[i] <= 2^31 - 1,(也就是说,每个整数都符合 32 位有符号整数类型);
F.length >= 3;
对于所有的0 <= i < F.length - 2,都有 F[i] + F[i+1] = F[i+2] 成立。
另外,请注意,将字符串拆分成小块时,每个块的数字一定不要以零开头,除非这个块是数字 0 本身。
返回从 S 拆分出来的任意一组斐波那契式的序列块,如果不能拆分则返回 []
示例 1:
输入:"123456579"
输出:[123,456,579]
示例 2:
输入: "11235813"
输出: [1,1,2,3,5,8,13]
示例 3:
输入: "112358130"
输出: []
解释: 这项任务无法完成。
示例 4:
输入:"0123"
输出:[]
解释:每个块的数字不能以零开头,因此 "01","2","3" 不是有效答案。
示例 5:
输入: "1101111"
输出: [110, 1, 111]
解释: 输出 [11,0,11,11] 也同样被接受。
方法一:回溯 + 剪枝
将给定的字符串拆分成斐波那契式序列,可以通过回溯的方法实现。
使用列表存储拆分出的数,回溯过程中维护该列表的元素,列表初始为空。遍历字符串的所有可能的前缀,作为当前被拆分出的数,然后对剩余部分继续拆分,直到整个字符串拆分完毕。
根据斐波那契式序列的要求,从第 33 个数开始,每个数都等于前 22 个数的和,因此从第 33 个数开始,需要判断拆分出的数是否等于前 22 个数的和,只有满足要求时才进行拆分,否则不进行拆分。
回溯过程中,还有三处可以进行剪枝操作。
拆分出的数如果不是 00,则不能以 00 开头,因此如果字符串剩下的部分以 00 开头,就不需要考虑拆分出长度大于 11 的数,因为长度大于 11 的数以 00 开头是不符合要求的,不可能继续拆分得到斐波那契式序列;
拆分出的数必须符合 3232 位有符号整数类型,即每个数必须在 [0,2^{31}-1][0,2 31 −1] 的范围内,如果拆分出的数大于 2^{31}-12
31
−1,则不符合要求,长度更大的数的数值也一定更大,一定也大于 2^{31}-12
31
−1,因此不可能继续拆分得到斐波那契式序列;如果列表中至少有 22 个数,并且拆分出的数已经大于最后 22 个数的和,就不需要继续尝试拆分了。
当整个字符串拆分完毕时,如果列表中至少有 33 个数,则得到一个符合要求的斐波那契式序列,返回列表。如果没有找到符合要求的斐波那契式序列,则返回空列表。
实现方面,回溯需要带返回值,表示是否存在符合要求的斐波那契式序列。
class Solution {
public List<Integer> splitIntoFibonacci(String S) {
List<Integer> list = new ArrayList<Integer>();
abb(list, S, S.length(), 0, 0, 0);
return list;
}
public boolean abb(List<Integer> list, String S, int length, int index, int sum, int prev){
if(index == length){
return list.size() >= 3; //长度
}
long currLong = 0;
for(int i = index; i < length; i++){
if(i > index && S.charAt(index) == '0'){ //开头不能为0
break;
}
currLong = currLong * 10 + S.charAt(i) - '0';
if(currLong > Integer.MAX_VALUE){
break;
}
int curr = (int) currLong;
if(list.size() >= 2){
if(curr < sum){
continue;
} else if(curr > sum){
break;
}
}
list.add(curr);
if(abb(list, S, length, i + 1, prev + curr, curr)){
return true;
} else {
list.remove(list.size() - 1);
}
}
return false;
}
}
62.不同路径
一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为“Start” )。
机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为“Finish”)。
问总共有多少条不同的路径?
示例 1:
输入: m = 3, n = 2
输出: 3
解释:
从左上角开始,总共有 3 条路径可以到达右下角。
1. 向右 -> 向右 -> 向下
2. 向右 -> 向下 -> 向右
3. 向下 -> 向右 -> 向右
方法一:动态规划
class Solution {
public int uniquePaths(int m, int n) {
//二维数组 X,Y起始位
int[] [] f = new int[m][n];
for(int i = 0; i < m; ++i){
f[i][0] = 1;
}
for(int j = 0; j < n; ++j){
f[0][j] = 1;
}
for(int i = 1; i < m; ++i){
for(int j = 1; j < n; ++j){
f[i][j] = f[i - 1][j] + f[i][j - 1];
}
}
return f[m - 1][n - 1];
}
}
方法二:组合数学
class Solution {
public int uniquePaths(int m, int n) {
long ans = 1;
for (int x = n, y = 1; y < m; ++x, ++y) {
ans = ans * x / y;
}
return (int) ans;
}
}
860. 柠檬水找零
在柠檬水摊上,每一杯柠檬水的售价为 5 美元。
顾客排队购买你的产品,(按账单 bills 支付的顺序)一次购买一杯。
每位顾客只买一杯柠檬水,然后向你付 5 美元、10 美元或 20 美元。你必须给每个顾客正确找零,也就是说净交易是每位顾客向你支付 5 美元。
注意,一开始你手头没有任何零钱。
如果你能给每位顾客正确找零,返回 true ,否则返回 false .
示例 1:
输入:[5,5,5,10,20]
输出:true
解释:
前 3 位顾客那里,我们按顺序收取 3 张 5 美元的钞票。
第 4 位顾客那里,我们收取一张 10 美元的钞票,并返还 5 美元。
第 5 位顾客那里,我们找还一张 10 美元的钞票和一张 5 美元的钞票。
由于所有客户都得到了正确的找零,所以我们输出 true。
示例 2:
输入:[5,5,10]
输出:true
示例 3:
输入:[10,10]
输出:false
方法一:模拟 + 贪心
由于顾客只可能给你三个面值的钞票,而且我们一开始没有任何钞票,因此我们拥有的钞票面值只可能是 55 美元,1010 美元和 2020 美元三种。基于此,我们可以进行如下的分类讨论。
- 5 美元,由于柠檬水的价格也为 55 美元,因此我们直接收下即可。
- 10 美元,我们需要找回 55 美元,如果没有 55 美元面值的钞票,则无法正确找零。
- 20 美元,我们需要找回 1515 美元,此时有两种组合方式,一种是一张 1010 美元和 55 美元的钞票,一种是 33 张 55 美元的钞票,如果两种组合方式都没有,则无法正确找零。当可以正确找零时,两种找零的方式中我们更倾向于第一种,即如果存在 55 美元和 1010 美元,我们就按第一种方式找零,否则按第二种方式找零,因为需要使用 55 美元的找零场景会比需要使用 1010 美元的找零场景多,我们需要尽可能保留 55 美元的钞票。
class Solution {
public boolean lemonadeChange(int[] bills) {
//数组遍历 判断5 累加 five 杯数
// 判断10 在判断five 0 退出返回 false
// 5 累加 five 10累加ten
int five = 0, ten = 0;
for(int bill : bills){
if(bill == 5){
five++;
} else if(bill == 10){
if(five == 0){
return false;
}
five--;
ten++;
} else {
if(five > 0 && ten > 0){
five--;
ten--;
} else if(five >= 3){
five -= 3;
} else {
return false;
}
}
}
return true;
}
}
649. Dota2 参议院
Dota2 的世界里有两个阵营:Radiant(天辉)和 Dire(夜魇)
Dota2 参议院由来自两派的参议员组成。现在参议院希望对一个 Dota2 游戏里的改变作出决定。他们以一个基于轮为过程的投票进行。在每一轮中,每一位参议员都可以行使两项权利中的一项:
禁止一名参议员的权利:
参议员可以让另一位参议员在这一轮和随后的几轮中丧失所有的权利。
宣布胜利:
如果参议员发现有权利投票的参议员都是同一个阵营的,他可以宣布胜利并决定在游戏中的有关变化。
给定一个字符串代表每个参议员的阵营。字母 “R” 和 “D” 分别代表了 Radiant(天辉)和 Dire(夜魇)。然后,如果有 n 个参议员,给定字符串的大小将是 n。
以轮为基础的过程从给定顺序的第一个参议员开始到最后一个参议员结束。这一过程将持续到投票结束。所有失去权利的参议员将在过程中被跳过。
假设每一位参议员都足够聪明,会为自己的政党做出最好的策略,你需要预测哪一方最终会宣布胜利并在 Dota2 游戏中决定改变。输出应该是 Radiant 或 Dire
示例 1:
输入:"RD"
输出:"Radiant"
解释:第一个参议员来自 Radiant 阵营并且他可以使用第一项权利让第二个参议员失去权力,因此第二个参议员将被跳过因为他没有任何权利。然后在第二轮的时候,第一个参议员可以宣布胜利,因为他是唯一一个有投票权的人
方法一: 循环 队列
思路与算法
我们以天辉方的议员为例。当一名天辉方的议员行使权利时:
如果目前所有的议员都为天辉方,那么该议员可以直接宣布天辉方取得胜利;
如果目前仍然有夜魇方的议员,那么这名天辉方的议员只能行使「禁止一名参议员的权利」这一项权利。显然,该议员不会令一名同为天辉方的议员丧失权利,所以他一定会挑选一名夜魇方的议员。那么应该挑选哪一名议员呢?容易想到的是,应该贪心地挑选按照投票顺序的下一名夜魇方的议员。这也是很容易形象化地证明的:既然只能挑选一名夜魇方的议员,那么就应该挑最早可以进行投票的那一名议员;如果挑选了其它较晚投票的议员,那么等到最早可以进行投票的那一名议员行使权利时,一名天辉方议员就会丧失权利,这样就得不偿失了。
由于我们总要挑选投票顺序最早的议员,因此我们可以使用两个队列 \textit{radiant}radiant 和 \textit{dire}dire 分别按照投票顺序存储天辉方和夜魇方每一名议员的投票时间。随后我们就可以开始模拟整个投票的过程:
如果此时 radiant 或者 dire 为空,那么就可以宣布另一方获得胜利;
如果均不为空,那么比较这两个队列的首元素,就可以确定当前行使权利的是哪一名议员。如果 radiant 的首元素较小,那说明轮到天辉方的议员行使权利,其会挑选 dire 的首元素对应的那一名议员。因此,我们会将dire 的首元素永久地弹出,并将 radiant 的首元素弹出,增加 nn 之后再重新放回队列,这里 nn 是给定的字符串senate 的长度,即表示该议员会参与下一轮的投票。
为什么这里是固定地增加 nn,而不是增加与当前剩余议员数量相关的一个数?读者可以思考一下这里的正确性。
同理,如果 dire 的首元素较小,那么会永久弹出 radiant 的首元素,剩余的处理方法也是类似的。
这样一来,我们就模拟了整个投票的过程,也就可以得到最终的答案了。
class Solution {
public String predictPartyVictory(String senate) {
int n = senate.length();
//Queue 队列
Queue<Integer> radiant = new LinkedList<Integer>();
Queue<Integer> dire = new LinkedList<Integer>();
for(int i = 0; i < n; ++i){
if(senate.charAt(i) == 'R'){
radiant.offer(i);//得到返回false
} else {
dire.offer(i);
}
}
while(!radiant.isEmpty() && !dire.isEmpty()){
//remove() 和 poll() 方法都是从队列中删除第一个元素。remove() 的行为与 Collection 接口的版本相似, 但是新的 poll () 方法在用空集合调用时不是抛出异常,只是返回 null。因此新的方法更适合容易出现异常条件的情况
int radiantIndex = radiant.poll(), direIndex = dire.poll();
if(radiantIndex < direIndex){
radiant.offer(radiantIndex + n);
} else {
dire.offer(direIndex + n);
}
}
return !radiant.isEmpty() ? "Radiant" : "Dire";
}
}
376.摆动序列
如果连续数字之间的差严格地在正数和负数之间交替,则数字序列称为摆动序列。第一个差(如果存在的话)可能是正数或负数。少于两个元素的序列也是摆动序列。
例如, [1,7,4,9,2,5] 是一个摆动序列,因为差值 (6,-3,5,-7,3) 是正负交替出现的。相反, [1,4,7,2,5] 和 [1,7,4,5,5] 不是摆动序列,第一个序列是因为它的前两个差值都是正数,第二个序列是因为它的最后一个差值为零。
给定一个整数序列,返回作为摆动序列的最长子序列的长度。 通过从原始序列中删除一些(也可以不删除)元素来获得子序列,剩下的元素保持其原始顺序。
class Solution {
public int wiggleMaxLength(int[] nums) {
int n = nums.length;
if (n < 2) {
return n;
}
int[] up = new int[n];
int[] down = new int[n];
up[0] = down[0] = 1;
for (int i = 1; i < n; i++) {
if (nums[i] > nums[i - 1]) {
up[i] = Math.max(up[i - 1], down[i - 1] + 1);
down[i] = down[i - 1];
} else if (nums[i] < nums[i - 1]) {
up[i] = up[i - 1];
down[i] = Math.max(up[i - 1] + 1, down[i - 1]);
} else {
up[i] = up[i - 1];
down[i] = down[i - 1];
}
}
return Math.max(up[n - 1], down[n - 1]);
}
}
217.存在重复元素
给定一个整数数组,判断是否存在重复元素。
如果任意一值在数组中出现至少两次,函数返回 true
。如果数组中每个元素都不相同,则返回 false
。
示例 1:
输入: [1,2,3,1]
输出: true
示例 2:
输入: [1,2,3,4]
输出: false
class Solution {
public boolean containsDuplicate(int[] nums) {
//方法有很多 已知好像有十五种 比如 map 排序 数组 索引
Arrays.sort(nums);
int n = nums.length;
for(int i = 0; i < n - 1; i++){
if(nums[i] == nums[i + 1]){
return true;
}
}
return false;
}
}
作者:LeetCode-Solution
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。