【Q287】(md) 寻找重复数
给定一个包含 n + 1 个整数的数组 nums,其数字都在 1 到 n 之间(包括 1 和 n),可知至少存在一个重复的整数。假设只有一个重复的整数,找出这个重复的数。
示例 1:
输入: [1,3,4,2,2]
输出: 2
示例 2:
输入: [3,1,3,4,2]
输出: 3
说明(!!!):不能更改原数组(假设数组是只读的)。
只能使用额外的 O(1) 的空间。
时间复杂度小于 O(n2) 。
数组中只有一个重复的数字,但它可能不止重复出现一次。
class Solution {
/*
* 【Floyd 判圈算法】
* Floyd判圆算法常见于链表数据结构——一部分直线链表+一部分圆形循环链表
* 数学证明略(基本原理类似于两人一快一慢在操场跑步),其实利也用了【双指针】中的【快慢指针】
* 记住三句关键的话(!!!):
* 1.slow和fast从链表头出发,slow走一步,fast走两步,若相遇(slow==fast)则存在环
* 2.slow放回链表头,fast从其相遇的地方开始,slow走一步,fast走一步,相遇点为环的起点(入点)
* 3.slow不动,fast前进,再次相遇时走的fast刚刚走的步数为环的大小
* (其实第一步fast走三步也行,只是后面会更复杂;第二步哪个放回表头都可以;第三部哪个不动哪个走都可以)
*
* 【关键】将该数组抽象成链表
* 举几个例子:nums = [1, 3, 4, 2, 2] 1 → 3 → 2 → 4 → 2
* nums = [2, 5, 9, 6, 9, 3, 8, 9, 7, 1] 2 → 9 → 1 → 5 → 3 → 6 → 8 → 7 → 9
*
* 这道题等价于:找出循环圆圈的入点。
*
* 来看看Fliyd判圆算法的复杂度是否符合要求:
* 时间复杂度:O(n)。Fliyd判圆法是线性的时间复杂度
* 空间复杂度:O(1)。我们只需要常数空间存放若干变量
*/
public int findDuplicate(int[] nums) {
int slow = 0;
int fast = 0;
do {
slow = nums[slow];
fast = nums[nums[fast]];
} while(slow != fast);
slow = 0;
while(slow != fast) {
slow = nums[slow];
fast = nums[fast];
}
return slow;
}
}
class Solution {
/*
* 【一个别样的二分法】
* 这是一个奇怪的二分法:low和high不是"下标",而是"值"; 减治的依据是个数,而非大小
*
* 举个例子:nums = [2, 4, 5, 2, 3, 1, 7, 6]
* low = 1,high = 7,因此mid = 4,大于等于4的元素有5个,因此这个重复的数字在[1, 4]
*
* 大致就是上面这样的思路。
* 另外要明确,【二分法】实际上是【减治】的思想,即每次去掉不可能为最终结果的部分,从而缩小范围
* 因此每次"去掉的部分"必须保证其不可能包含最终答案。之所以强调这种思想,是因为这总是涉及到二分时大于or大于等于,小于or小于等于的问题
* 这种安全性通常可以由某个极短的特殊值进行确定(比如本题取 [ 1 1 2 ]和[ 2 2 1 ])
*
*/
public int findDuplicate(int[] nums) {
int low = 1;
int high = nums.length - 1;
int mid = (low + high) / 2;
while(low < high) {
mid = (low + high) / 2;
int count = 0;
for(int n : nums) {
if(n <= mid) {
count++;
}
}
if(count > mid) {
high = mid;
}else {
low = mid + 1;
}
}
return low;
}
// 复杂度分析:
// 时间复杂度:二分法时间复杂度为O(logn);计数count需要遍历整个数组,为O(n);二者嵌套,总复杂度为O(nlogn)
// 空间复杂度:只需要若干常数空间,复杂度O(1)
//
// 通过时间和空间复杂度的分析我们可以看出:这个【二分】的特殊还体现在,它反其道而行之,用【时间】换【空间】
}
【Q394】(md) 字符串解码
给定一个经过编码的字符串,返回它解码后的字符串。
编码规则为: k[encoded_string],表示其中方括号内部的 encoded_string 正好重复 k 次。注意 k 保证为正整数。
你可以认为输入字符串总是有效的;输入字符串中没有额外的空格,且输入的方括号总是符合格式要求的。
此外,你可以认为原始数据不包含数字,所有的数字只表示重复的次数 k ,例如不会出现像 3a 或 2[4] 的输入。
示例:s = “3[a]2[bc]” 返回 “aaabcbc”.
s = “3[a2[c]]” 返回 “accaccacc”.
s =“2[abc]3[cd]ef” 返回 “abcabccdcdcdef”.
class Solution {
/*
* 【辅助栈】中的【双栈】
* 【辅助栈】不一定是Stack,有时候用LinkedList也可以,甚至ArrayList;有时候也用Queue(【辅助队列】)
*
* 关键点在于两个辅助串tempNum,tempRes和两个辅助栈satckNum,stackRes
*
* 遍历一次字符数组:
* // 当遍历到数字字符时候,连接到数字暂存串上
* // 当遍历到字母时,直接连接到暂存串tempRes上
* // 当遍历到'['时,辅助串都各自入栈,辅助串各自清空
* // 当遍历到']'时",出栈,给串赋值
*
* 遇到'['时的"入栈"和"清空串",实际上将最终结果从从串转移到了栈中;
* 遇到']'时的"出栈"和"给串赋值",实际上又将最终结果从栈转移到了串
* 这样可谓巧妙 —— tempRes = stackRes.removeLast() + tempRes * stackNum.removeLast(), 解决了并列问题,不需要进行栈空判断:2[a]3[b]
*
* 根据上面的步骤,在纸上顺着写一遍这几个例子,一定能明白(看着例子敲代码):
* 3[a2[c]] ——— 理解栈的作用:一次遍历,也能解决[]的嵌套问题
* a[a]2[b] ——— 理解tempRes的清空、暂存串和栈顶内容的转化,是如何解决这样的并列问题的?
*/
public String decodeString(String s) {
String tempNum = "";
String tempRes = "";
LinkedList<Integer> stackNum = new LinkedList<>();
LinkedList<String> stackRes = new LinkedList<>();
for(char c : s.toCharArray()) {
if(Character.isDigit(c)) {
// 当遍历到数字字符时候,连接到数字暂存串上
tempNum += c;
} else if(c == '[') {
// 当遍历到'['时,辅助串都各自入栈,辅助串各自清空
stackNum.addLast(Integer.valueOf(String.valueOf(tempNum)));
stackRes.addLast(tempRes);
tempNum = "";
tempRes = "";
} else if(c == ']') {
// 当遍历到']'时",出栈,给串赋值
// 这里代码的实际上完成了:tempRes = stackRes.removeLast() + tempRes * stackNum.removeLast()
StringBuilder sb = new StringBuilder();
String temp = stackRes.removeLast();
int n = stackNum.removeLast();
for(int i = 0; i < n; i++) {
sb.append(tempRes);
}
tempRes = sb.insert(0, temp).toString();
} else {
// 当遍历到字母时,直接连接到暂存串tempRes上
tempRes += c;
}
}
return tempRes;
}
/*
* 单个栈同样可以实现,但需要进行多次判断
*
* 不妨使用两个栈(一个存数字,一个存字符串)
* 因为入栈操作是同时进行的,两个栈的对称程度极其优雅
*
* 两个栈还对应个两个暂存串(一个存数字,一个存字符串)
* 因为会出现12[a]和3[loli]这样的情况,因此我们需要用暂存12和loli,然后整体、同时入栈
*
* 一切都是对称的。
*/
}
【Q198】(ez) 打家劫舍
你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相
互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。
给定一个代表每个房屋存放金额的非负整数数组,计算你 不触动警报装置的情况下 ,一夜之内能够偷窃到的最高金额。
示例 1:输入: [1,2,3,1]
输出: 4
示例 2:输入: [2,7,9,3,1]
输出: 12
class Solution {
public int rob(int[] nums) {
/*
* 【动态规划】
* 比较经典的送分题了
*
* 就算如此,我也不是马上想到dp:这是一个思维过程:
* 1.我不是直接找转移方程,而是先考虑这是dp还是回溯(它们在直觉上很像)
* 2.【回溯】的特点是,在当前位置,你无法确定他是不是最优解(最终结果),因为后面的数字或字母会将目前的结果完全推翻
* 3.【动态规划】的本质是【贪心】,“以该位置为结尾的子串中”,能保证这是最优解
*/
int len = nums.length;
if(len == 0) return 0;
int[] dp = new int[len + 1];
dp[0] = 0;
dp[1] = nums[0];
for(int i = 2; i < len + 1 ;i++) {
dp[i] = Math.max(dp[i - 1], dp[i - 2] + nums[i - 1]);
}
return dp[len];
}
}
Qs from https://leetcode-cn.com
♠ loli suki
♦ end