【LeetCode】Sama的个人记录_12

 

 

【Q1371】(md) 每个元音包含偶数次的最长字符串
 
给你一个字符串 s ,请你返回满足以下条件的最长子字符串的长度:每个元音字母,即 ‘a’,‘e’,‘i’,‘o’,‘u’ ,在子字符串中都恰好出现了偶数次。
 
示例 1:

   输入:s = “eleetminicoworoep”
   输出:13
   解释:最长子字符串是 “leetminicowor” ,它包含 e,i,o 各 2 个,以及 0 个 a,u 。
 
示例 2:

   输入:s = “leetcodeisgreat”
   输出:5
   解释:最长子字符串是 “leetc” ,其中包含 2 个 e 。
 
示例 3:

   输入:s = “bcbcbc”
   输出:6
   解释:这个示例中,字符串 “bcbcbc” 本身就是最长的,因为所有的元音 a,e,i,o,u都出现了 0 次。
 
提示: 1 <= s.length <= 5

class Solution {
     
	/*
	 * 【状态压缩】【哈希表】【前缀和】
	 * 
	 * 对于aexye,遍历一遍,发现每个位置都可以用一串数字表示状态(利用异或(^)可以写出每个位置的状态):
	 * 00000(-1位置), 10000, 11000, 11000, 11000,10000
	 * 又发现,在a位置,和第二个e的位置,其状态是相同的!!!我们把这两个位置相减就得到了一个符合条件的字符串!
	 * 基本思路:
	 * 		第一次出现此状态时,就需要记录到下标,然后下次遇到相同状态,计算最大长度(我们记录的是第一次出现此状态的位置,不能再去更新它!)
	 * 		即字典保存状态一次出现的位置(★☆★)
	 * 
	 * 遍历 :
	 * 	     如果是aeiou:
	 * 		    对状态进行一次异或(偶数次的a就能回到原先的状态,e..同理。因此用异或)
	 * 		    用putIfAbsent(如果是第一次遇到该状态,就记录它及其位置;如果不是一次遇到,不要更新它)
	 * 	     不管是不是aeiou,只要此时状态之前出现过,就尝试更新最大长度
	 * 		
	 */
    public int findTheLongestSubstring(String s) {
     
    	int maxLen = 0;
    	int currentState = 0x00000;
    	Map<Character, Integer> vowel = new HashMap<Character, Integer>();
    	Map<Integer, Integer> state = new HashMap<Integer, Integer>();
    	vowel.put('a', 0x00001);
    	vowel.put('e', 0x00010);
    	vowel.put('i', 0x00100);
    	vowel.put('o', 0x01000);
    	vowel.put('u', 0x10000);
    	state.put(0, -1);
    	for(int i = 0 ; i < s.length() ; i++) {
     
    		if(vowel.containsKey(s.charAt(i))) {
     
    			currentState ^= vowel.get(s.charAt(i));
    			state.putIfAbsent(currentState, i);		// 使用putIfAbsent方法添加键值对,如果已经存在对应的值,则依旧为原来的值
    		}
    		if(state.containsKey(currentState)) {
     
    			maxLen = Math.max(maxLen, i - state.get(currentState));
    		}
    	}
    	return maxLen;
    }
}

 

 

【Q39】(md) 组合总和
 
给定一个无重复元素的数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target的组合。
 
candidates 中的数字可以无限制重复被选取。
 
说明:所有数字(包括 target)都是正整数。 解集不能包含重复的组合。
 
示例 1:
   输入: candidates = [2,3,6,7], target = 7, 所求解集为: [ [7], [2,2,3] ]
 
示例2:

   输入: candidates = [2,3,5], target = 8, 所求解集为: [ [2,2,2,2], [2,3,3], [3,5] ]

class Solution {
     
	/*
	 * 【回溯算法】
	 * 
	 * 画一画回溯图,思路其实很清晰的。但是我们从这题中,可以发现一些极其重要的细节:
	 * 		如果暂存串是个字符串或者是个数字,那么traceback(...str + 'a'..)和traceback(...n+1..)
	 * 	这样写毫无问题
	 * 		但当暂存串是个StringBuffer或者ArrayList时,我们就能向上面那样【修改和传递直接一起写在traceback中】
	 *  我们需要先【add,再traceback,再remove】,即需要手动让暂存串回到刚才的位置——毕竟回溯时用的是一个公共暂存串
	 *  	另外,在给res进行add时,add进去的一定是此时的暂存串的拷贝!否则add进去的是个引用,它随时变化!!!
	 *  
	 *      此题【回溯】时的遇到的问题,在【Leetcode 46 全排列】中同样出现(个人记录8中)
	 */
    public List<List<Integer>> combinationSum(int[] candidates, int target) {
     
    	Arrays.sort(candidates);
    	int len = candidates.length;
    	List<Integer> tempList = new ArrayList<Integer>();
    	
    	traceback(tempList, candidates, 0, len, 0, target);
    	return res;
    }
    public List<List<Integer>> res = new ArrayList<>();
    private void traceback(List<Integer> tempList, int[] candidates, int n, int len, int sum, int target) {
     
    	if(sum == target) {
     
    		res.add(new ArrayList<Integer>(tempList));
    		return;
    	}
    	if(sum > target) {
     
    		return;
    	}
    	for(int i = n ; i < len ; i++) {
     
    		tempList.add(candidates[i]);
    		traceback(tempList, candidates, i, len, sum + candidates[i], target);
    		tempList.remove(tempList.size() - 1);
    	}
    }
}

 

 

【Q40】(md) 组合总和Ⅱ
 
给定一个数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。
 
candidates 中的每个数字在每个组合中只能使用一次。
 
说明:所有数字(包括目标数)都是正整数。 解集不能包含重复的组合。
 
示例 1:

   输入: candidates = [10,1,2,7,6,1,5], target = 8,
   所求解集为: [ [1, 7], [1, 2, 5], [2, 6], [1, 1, 6] ]
 
示例 2:

   输入: candidates = [2,5,2,1,2], target = 5,
   所求解集为: [ [1,2,2], [5] ]

class Solution {
     
	/*
	 * 和上题【Leetcode39组合总和】的思路几乎完全一致的【回溯算法】
	 * 
	 * 首先和上题一样,有需要手动回溯状态的地方、以及add进的是tempList的拷贝而不是引用本身的问题,都需要万分注意
	 * 一个不同点是,每个数只能用一次,因此traceback内的for循环起始点不一样(这并不难)
	 * 
	 * 真正的难点在于【避免重复】
	 * 以[1, 2, 2, 3, 5],target=8 为例,我们正常的回溯算法会出现[1, 2, 5][1, 2, 5]的重复
	 * 我们或许可以这样解决:
	 * 在for循环时,如果candidates[i] == candidates[i - 1],也就是此时用到的数字在上一个已经用过了,就continue(跳过此时重复的数字)
	 * 这有效避免了[1, 2, 5]的重复——因为第二个2被跳过了;但是[1, 2, 2, 3]这个结果就没了(因为第二个2被无情的跳过了)
	 * 
	 * ★☆★ 一个精妙的语句:if(candidates[i] == candidates[i - 1] && i > layer)
	 * 这使得i==layer时候此时的数字不会被跳过,也就是说每轮for循环的第一个起始数字,必定合法!!
	 * 这样[1,2,2,3]的第二个2,没有被跳过,因为它是顺序写下来的(for循环的第一个);对于[1,2,5],2是for循环第二个才遍历到的,符合if条件,被跳过
	 * 
	 */
    public List<List<Integer>> combinationSum2(int[] candidates, int target) {
     
    	Arrays.sort(candidates);
    	ArrayList<Integer> tempList = new ArrayList<>();
    	traceback(tempList, candidates, 0, 0, target);
    	return res;
    	
    }
    private List<List<Integer>> res = new ArrayList<>();
    private void traceback(ArrayList<Integer> tempList, int[] candidates, int layer, int sum, int target) {
     
    	if(sum == target) {
     
    		res.add(new ArrayList<Integer>(tempList));
    		return;
    	}
    	if(sum > target) {
     
    		return;		// 剪枝
    	}
    	for(int i = layer ; i < candidates.length ; i++) {
     
    		if(i > 0 && candidates[i] == candidates[i - 1] && i > layer) {
     
    			continue;
    		}
    		tempList.add(candidates[i]);
    		traceback(tempList, candidates, i + 1, sum + candidates[i], target);
    		tempList.remove(tempList.size() - 1);	// 手动回溯tempList的状态
    	}
    }
    // 在重复一遍本题避免重复的关键代码:if(candidates[i] == candidates[i - 1] && i > layer)
}

 
 

 

 

 

 

 

 

 

 

 

Qs from https://leetcode-cn.com
♥ loli suki
♣ end

你可能感兴趣的:(Leetcode,算法,leetcode,java)