数组篇(一)
不要纠结,干就完事了,熟练度很重要!!!多练习,多总结!!!
LeetCode46:全排列
给定一个 没有重复 数字的序列,返回其所有可能的全排列。
解题思路
经典的回溯思路!我们把回溯的过程想象成一个树形结构,其实代码的执行过程也是如此!比如[1,2,3]的全排列,我们知道有6种,树形结构的第一层有三种选择1,2,3,然后第二层我们在剩余的数字中再进行选择直至最后一层。例如:当第一层选1时,第二层有2,3两种选择,其中第二层为2时,第三层只能为3,第二层选3时,第三层只能为2。其余同理,结合代码理解下回溯思想,这个很经典必须掌握!!!
代码实现
class Solution {
List<List<Integer>> list=new ArrayList<>();
public List<List<Integer>> permute(int[] nums) {
dfs(nums,new ArrayList<>());
return list;
}
public void dfs(int[] nums,List<Integer> tmp){
if (tmp.size()==nums.length){
list.add(new ArrayList<>(tmp));
}
for(int num:nums){
if (!tmp.contains(num)){
tmp.add(num);
dfs(nums,tmp);
tmp.remove(tmp.size()-1);
}
}
}
}
LeetCode78:子集
给定一组不含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)。
说明:解集不能包含重复的子集
解题思路
紧接着上一题全排列,这题类似同样是回溯的思想!不同的是在全排列是在dfs时我们需要判断每次排列的长度等于原始集合的长度时才认为找到一个新的排列,但是本题就没有此限制。即N个元素的子集个数为2^N个!
代码实现
class Solution {
List<List<Integer>> list=new ArrayList<>();
public List<List<Integer>> subsets(int[] nums) {
dfs(nums,0,new ArrayList<>());
return list;
}
public void dfs(int[] nums,int start,List<Integer> tmp){
for (int i=start;i<nums.length;i++){
tmp.add(nums[i]);
dfs(nums,i+1,tmp);
tmp.remove(tmp.size()-1);
}
list.add(new ArrayList<>(tmp));
}
}
剑指offer38:字符串的排列
输入一个字符串,打印出该字符串中字符的所有排列。
示例:
输入:s = “abc”
输出:[“abc”,“acb”,“bac”,“bca”,“cab”,“cba”]
解题思路
这题与全排列思路较为一致,区别是全排列是int数组,输出为list,而本题输出的是string类型,我们对字符串数组进行操作,将每一种组合list转为string过于麻烦,直接对于字符串数组进行操作方便些,所以依旧采用dfs思想,只是在剪纸去重上引用set,每种组合采用对字符数组原地交换为准则!看代码领悟下吧!
代码实现
class Solution {//方法一
List<String> list=new ArrayList<>();
public String[] permutation(String s) {
if (s==null||s.length()==0){
return null;
}
char[] ch=s.toCharArray();
dfs(ch,0);
return list.toArray(new String[list.size()]);
}
public void dfs(char[] ch,int x){
if (x== ch.length-1){
list.add(String.valueOf(ch));
return;
}
HashSet<Character> set=new HashSet<>();
for (int i=x;i<ch.length;i++){
if (set.contains(ch[i])){
continue;
}
set.add(ch[i]);
swap(ch,i,x);
dfs(ch,x+1);
swap(ch,i,x);
}
}
public void swap(char[] ch,int i,int j){
char tmp=ch[i];
ch[i]=ch[j];
ch[j]=tmp;
}
}
class Solution {//方法二:此方法与上述全排列比较类似,只是考虑了有重复元素的情况,可以对比来分析记忆。
List<String> list=new ArrayList<>();
public String[] permutation(String s) {
if (s==null||s.length()==0){
return null;
}
char[] ch=s.toCharArray();
boolean[] used=new boolean[ch.length];//这是剪枝用的,也就是重复元素的全排列才需要这个
Arrays.sort(ch);//重复元素的全排列问题,需要先进行排序操作,下面会用到
dfs(ch,used,new ArrayList<>());
return list.toArray(new String[list.size()]);
}
public void dfs(char[] ch,boolean[] used,ArrayList<Character> l){
if (l.size()==ch.length){//这操作和全排列问题是一样的模板
StringBuilder sb=new StringBuilder();
for (char c:l){
sb.append(c);
}
list.add(sb.toString());
}
for (int i=0;i<ch.length;i++){
if (used[i]){//已经被访问的直接跳过
continue;
}
if (i>0&&ch[i]==ch[i-1]&&!used[i-1]){//这个很关键,三要素要记住:
//1.元素索引大于0,不然你怎么和左边的元素对比? 2.判断和左边的是否相等
//3.相等的话,左边的是否被访问过,如果访问过,那不算重复,没有访问那么需要剪枝,因为会出现重复情况
continue;
}
l.add(ch[i]);
used[i]=true;
dfs(ch,used,l);
l.remove(l.size()-1);
used[i]=false;
}
}
}
LeetCode60:第K个排列
给出集合 [1,2,3,…,n],其所有元素共有 n! 种排列。
按大小顺序列出所有排列情况,并一一标记,当 n = 3 时, 所有排列如下:
“123”
“132”
“213”
“231”
“312”
“321”
给定 n 和 k,返回第 k 个排列。
解题思路
本题要求的是全排列按顺序中的第k个!普遍给出的一个比较简单但是不太好理解的方法:康托展开与逆康托展开!这个概念以前没接触到过-_-!
首先康托展开是用来计算当前排列在所有由小到大全排列中的顺序,这与本题一致(对于关系一致)!
百度百科都有解释,而且还附有实现代码。。。!各位看一下算是接触一个新概念吧!
代码实现
class Solution {
public String getPermutation(int n, int k) {
StringBuffer sb=new StringBuffer();
List<Integer> candidate=new ArrayList<>();
int[] factorials=new int[n+1];
factorials[0]=1;
int fact=1;
for (int i=1;i<=n;i++){
candidate.add(i);
fact*=i;
factorials[i]=fact;
}
k-=1;
for (int i=n-1;i>=0;i--){
int index=k/factorials[i];
sb.append(candidate.remove(index));
k-=index*factorials[i];
}
return sb.toString();
}
}
LeetCode1:鹅厂三连击之两数之和
给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标。
解题思路
借助HashMap,题目要求返回的是和为target的元素下标,那么每次首先查询map中是否存在key为target-num[i]的,如果存在则找到了和为目标值的数组下标。未查找到则进行put操作,put时key为nums[i],value为i。
代码实现
class Solution {
public int[] twoSum(int[] nums, int target) {
int[] res=new int[2];
HashMap<Integer,Integer> map=new HashMap<>();
for (int i=0;i<nums.length;i++){
if (map.containsKey(target-nums[i])){
res[1]=i;
res[0]=map.get(target-nums[i]);
break;
}
map.put(nums[i],i);
}
return res;
}
}
LeetCode15:鹅厂三连击之三数之和
给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有满足条件且不重复的三元组。注意:答案中不可以包含重复的三元组。
解题思路
双指针!双指针!双指针!
三数之和和四数之和思路一致,所以对本题进行较为详细的解析,三个数我们可以固定一个数,然后用零减去固定的这个数,就是我们要去寻找的target值,怎么找呢?借助双指针,双指针初始位置为数组两边的元素(两边指的排除每次固定的元素后其余范围的),为了双指针的正确性需对数组进行排序这样才是准确的!首先我们从数组起点开始依次选择一个元素作为固定元素,那么我们接下来需要寻找的就是双指针指向的两个数之和等于零减去固定元素(即target)。当双指针元素之和小于target时,左指针右移,大于target时,右指针左移,当等于时记录下来,同时分别查看左指针的左右边和右指针的左边是否分别相等,如何相等需要跳过,因为此事出现了重复的情况!同时还有一种情况那就是我们每次在固定一个元素时,如果固定的元素已经大于零,我们需要结束,因为数组的排序的,如果固定元素已经大于0,那么在固定元素右侧已经不可能找出和为0的组合了!!!处理好两种边界情况,可以减少不必要的运算加快速度!
代码实现
class Solution {
public List<List<Integer>> threeSum(int[] nums) {
Arrays.sort(nums);
List<List<Integer>> res=new ArrayList<>();
for (int i=0;i<nums.length;i++){
int target=0-nums[i];
int left=i+1;
int right=nums.length-1;
if (nums[i]>0){
break;
}
if (i==0||nums[i]!=nums[i-1]){
while (left<right){
if (nums[left]+nums[right]==target){
res.add(Arrays.asList(nums[i],nums[left],nums[right]));
while (left<right&&nums[left]==nums[left+1]){
left++;
}
while (left<right&&nums[right]==nums[right-1]){
right--;
}
left++;
right--;
}else if (nums[left]+nums[right]<target){
left++;
}else {
right--;
}
}
}
}
return res;
}
}
LeetCode18:鹅厂三连击之四数之和
给定一个包含 n 个整数的数组 nums 和一个目标值 target,判断 nums 中是否存在四个元素 a,b,c 和 d ,使得 a + b + c + d 的值与 target 相等?找出所有满足条件且不重复的四元组。
注意:
答案中不可以包含重复的四元组。
解题思路
依旧是双指针的套路,思路同上三数之和,不进行过多讲解了,直接上代码!
代码实现
class Solution {
public List<List<Integer>> fourSum(int[] nums, int target) {
List<List<Integer>> res=new ArrayList<>();
List<List<Integer>> res1=new ArrayList<>();
Arrays.sort(nums);
for (int i=0;i<nums.length-2;i++){
for (int j=i+1;j<nums.length-2;j++){
int sum=target-(nums[i]+nums[j]);
int left=j+1;
int right=nums.length-1;
if (i==0||nums[i]!=nums[i-1]||nums[j]!=nums[j-1]){
while (left<right) {
if (nums[left] + nums[right] == sum) {
res.add(Arrays.asList(nums[i], nums[j], nums[left], nums[right]));
while (left < right && nums[left] == nums[left + 1]) {
left++;
}
while (left < right && nums[right] == nums[right - 1]) {
right--;
}
left++;
right--;
} else if (nums[left] + nums[right] < sum) {
left++;
} else {
right--;
}
}
}
}
}
for (List<Integer> tmp:res){
if (!res1.contains(tmp)){
res1.add(tmp);
}
}
return res1;
}
}
LeetCode6:Z字形变换
将一个给定字符串根据给定的行数,以从上往下、从左到右进行 Z 字形排列。比如输入字符串为 “LEETCODEISHIRING” 行数为 3 时,排列如下:
L C I R
E T O E S I I G
E D H N
解题思路
本题建议将例子按自己走一遍,你会发现它是有周期的,对!周期为2*row-2!那么你首先需要建立一个维度为行数的String数组,为什么是string,因为实际最后输出是一个二维数组的效果,如果你是string的一维数组,那么你每次可以对某一个元素进行字符串拼接操作来达到操作二维数组的效果!然后我们将目标字符串转换为字符数组,每次进行操作时将字符数组索引对周期进行取余运算,如果取余结果小于row,则直接对相应的string数组元素进行拼接,如果大于row,则对string数组的period-row元素进行拼接。最后只需将string数组的各个元素进行合并即可得到结果!
代码实现
class Solution {
public String convert(String s, int numRows) {
if (numRows==1){
return s;
}
String[] arr=new String[numRows];
Arrays.fill(arr,"");
char[] chars=s.toCharArray();
int len=chars.length;
int period=2*numRows-2;
for (int i=0;i<len;i++){
int mod=i%period;
if (mod<numRows){
arr[mod]+=chars[i];
}else {
arr[period-mod]+=chars[i];
}
}
StringBuffer sb=new StringBuffer();
for (String str:arr){
sb.append(str);
}
return sb.toString();
}
}
LeetCode54:螺旋矩阵
定一个包含 m x n 个元素的矩阵(m 行, n 列),请按照顺时针螺旋顺序,返回矩阵中的所有元素。
解题思路
本题题意很简单,实际操作有一些困难,我们如何能控制输出让其按照题目来进行“螺旋”输出呢?这个像是一个“碰壁”的过程,为什么这么说?我们从螺旋的轨迹可以看出,其就是先向右走到头,再向下走到头,向左走到头,再向上走到头,此为一个循环!后面循环这个过程,可以看出每次走一个循环,四个边界都会“缩短”,那么如何控制每次循环边界的变化就至关重要!我们依次定义四个边界:up=0,down=matrix.length-1,left=0,right=matrix[0].length-1;我们举例一个循环,首先从左至右时,up++,从上至下时right–,从右至左时,down–,从下至上时,left++。就是这样我们只要全程保证(left<=right&&up<=down)即可!
代码实现
class Solution {
public List<Integer> spiralOrder(int[][] matrix) {
List<Integer> res=new ArrayList<>();
if (matrix.length==0){
return res;
}
int up=0,down=matrix.length-1,left=0,right=matrix[0].length-1;
int x=0,y=0;
while (up<=down&&left<=right){
for (y=left;y<=right&&(left<=right&&up<=down);y++){
res.add(matrix[x][y]);
}
y--;
up++;
for(x=up;x<=down&&(left<=right&&up<=down);x++){
res.add(matrix[x][y]);
}
x--;
right--;
for (y=right;y>=left&&(left<=right&&up<=down);y--){
res.add(matrix[x][y]);
}
y++;
down--;
for (x=down;x>=up&&(left<=right&&up<=down);x--){
res.add(matrix[x][y]);
}
x++;
left++;
}
return res;
}
}
LeetCode59:螺旋矩阵Ⅱ
给定一个正整数 n,生成一个包含 1 到 n2 所有元素,且元素按顺时针顺序螺旋排列的正方形矩阵。
解题思路
这题和上面思路一模一样,区别只是一个是将矩阵元素螺旋输出,而本题是将矩阵进行螺旋赋值,其行动轨迹是一模一样的!所以实现思路也相同!
代码实现
class Solution {
public int[][] generateMatrix(int n) {
int[][] res=new int[n][n];
int up=0,down=n-1,left=0,right=n-1;
int x=0,y=0;
int m=1;
while (up<=down&&left<=right){
for (y=left;y<=right&&(up<=down&&left<=right);y++){
res[x][y]=m++;
}
up++;
y--;
for (x=up;x<=down&&(up<=down&&left<=right);x++){
res[x][y]=m++;
}
right--;
x--;
for (y=right;y>=left&&(up<=down&&left<=right);y--){
res[x][y]=m++;
}
down--;
y++;
for (x=down;x>=up&&(up<=down&&left<=right);x--){
res[x][y]=m++;
}
left++;
x++;
}
return res;
}
}
总结
本题来源于Leetcode中 归属于数组类型题目。
同许多在算法道路上不断前行的人一样,不断练习,修炼自己!
如有博客中存在的疑问或者建议,可以在下方留言一起交流,感谢各位!
觉得本博客有用的客官,可以给个赞鼓励下! 嘿嘿
喜欢本系列博客的可以关注下,以后除了会继续更新面试手撕代码文章外,还会出其他系列的文章!