46.全排列
全排列的概念我们在高中就学习过了,就题目所给的[1,2,3]三个数的全排列我们都知道一共有3 * 2 = 6个。
简单点就是第一位有3种选择,第二位有2种选择,第三位只有1中选择
,所以是3 * 2 * 1 = 6。
根据上面我们可以思考得到,其实就是针对每一位的放置需要进行特殊的考虑。
所以该题的话,我们可以采用回溯算法,通过更改对应位置(第一位,第二位。。。)的元素并添加到list中,并且递归到下一位(此时可选元素会 - 1),这样的方式完成该题目
//全排列
public List<List<Integer>> permute(int[] nums) {
List<List<Integer>> lists = new ArrayList<>();
if(nums.length == 0) {
return lists;
}
dfs(lists,new ArrayList<>(), nums, 0);
return lists;
}
public void dfs(List<List<Integer>> lists, List<Integer> list, int[] nums, int index) {
if(list.size() == nums.length) {
lists.add(new ArrayList<>(list));
return;
}
for(int i=index; i<nums.length; i++) {
//交换
swap(nums,index,i);
//添加自身(这里注意是添加 index 位置)
list.add(nums[index]);
dfs(lists, list, nums, index + 1);
//回溯
list.remove(list.size() - 1);
//交换回来(记得复原)
swap(nums,index,i);
}
}
public void swap(int[] nums, int index1, int index2) {
if(index1 == index2) {
return;
}
int temp = nums[index1];
nums[index1] = nums[index2];
nums[index2] = temp;
}
47.全排列2
该题与46不同点在于,它数组存在重复元素。
针对于重复元素不外乎就是防止重复处理,比如两个 0(1) 0(2)(我们这里对两个0进行标记),我们应该只处理一次 1 2 或者 2 1。
这道题我一开始使用的和46题相同的方法(交换的方法),但是由于交换会导致数组顺序改变(所以无法完成对重复元素的判断),所以这里的话我们采取另一个方式进行全排列的回溯搜(添加了一个boolean数组,对元素的访问进行记录)。
这里我们采用将重复元素按照顺序进行访问的方式进行使用(即如果碰到重复元素,你的前一个重复元素如果未使用则无法使用当前重复元素),通俗点说就是如果0(1)未使用则无法使用0(2)。
boolean[] flag;
//全排列
public List<List<Integer>> permuteUnique(int[] nums) {
Arrays.sort(nums);
List<List<Integer>> lists = new ArrayList<>();
if(nums.length == 0) {
return lists;
}
flag = new boolean[nums.length];
dfs(lists,new ArrayList<>(), nums, 0);
return lists;
}
public void dfs(List<List<Integer>> lists, List<Integer> list, int[] nums, int index) {
if(list.size() == nums.length) {
lists.add(new ArrayList<>(list));
return;
}
for(int i=0; i<nums.length; i++) {
//处理重复情况(代表重复部分是有顺序的)
//比如两个0(0(1),0(2),应该只处理一次,而不是处理两次)
if(flag[i] || (i > 0 && nums[i] == nums[i - 1] && flag[i - 1])) {
continue;
}
//代表已被访问
flag[i] = true;
//添加自身(这里注意是添加 index 位置)
list.add(nums[index]);
dfs(lists, list, nums, index + 1);
//回溯
list.remove(index);
flag[i] = false;
}
}
48.旋转图像
简单点说就是顺时针旋转90°一个二维数组。
这里的思路是先对该二维数组进行转置处理,然后再对每一行的数组进行数组反转。(该题更偏向一个数学的方向)
public void rotate(int[][] matrix) {
int n = matrix.length;
// transpose matrix
for (int i = 0; i < n; i++) {
for (int j = i; j < n; j++) {
int tmp = matrix[j][i];
matrix[j][i] = matrix[i][j];
matrix[i][j] = tmp;
}
}
// reverse each row
for (int i = 0; i < n; i++) {
for (int j = 0; j < n / 2; j++) {
int tmp = matrix[i][j];
matrix[i][j] = matrix[i][n - j - 1];
matrix[i][n - j - 1] = tmp;
}
}
}
49.字母异位词分组
字母异位即代表同样的字符个数和种类, 但是排列顺序不同。
这个题有个技巧是:使用字符排序,这样就可以判定那些字符串是异位词。(主要就是使用一个Map)
public List<List<String>> groupAnagrams(String[] strs) {
List<List<String>> lists = new ArrayList<>();
Map<String,List<String>> map = new HashMap<>();
for(String str : strs) {
char[] a = str.toCharArray();
Arrays.sort(a);
String ans = String.valueOf(a);
if(map.containsKey(ans)) {
map.get(ans).add(str);
}else {
List<String> list = new ArrayList<>();
list.add(str);
map.put(ans,list);
}
}
Set<String> set = map.keySet();
for(String str : set) {
lists.add(map.get(str));
}
return lists;
}
50.Pow(x,n)
该题目就是一个手写pow()方法的具体实现。
如果使用暴力法O(N)会导致超过规定时间,这里的话可以根据指数特性进行拆分(即二分法)。
众所周知,指数特性:(a为底数,n为指数)
- 当n为偶数时。a^n = a^(n / 2) * a^(n / 2)。
- 当n为奇数时,a^n = a^(n / 2) * a^(n / 2) * a。
通过这个数学公式,我们可以采用递归的方法,获得拆分后的数,再根据是否能被2整除进行选用采取哪种返回方式。
public double myPow(double x, int n) {
//底数为 0 则返回 0
if(x == 0.0) {
return 0.0;
}
//指数为 0,幂数不为 0,则返回 1
if(n == 0) {
return 1.0;
}
//标记指数的政府,后面
boolean symbol = true;
if(n < 0) {
symbol = false;
//如果为负数,则先反转并标记,等返回值时候再进行处理
n = -n;
}
//将指数折半,指数翻倍(这样时间复杂度为logn)
return symbol ? dfs(x,n) : 1.0 / dfs(x,n);
}
//将a^n 拆分成 a^n/2 * a^n/2 / a^n/2 * a^(n / 2) + 1 = a^n/2 * a^(n / 2) * a
//主要是分指数能否被2整除
public double dfs(double num, int n) {
if(n == 0) {
return 1.0;
}
double half = dfs(num,n/2);
return n % 2 == 0 ? half * half : half * half * num;
}