【LeetCode】Sama的个人记录_14

【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

猜你喜欢

转载自blog.csdn.net/m0_46202073/article/details/106348836