目录
一.求数组中的逆序对
1.题目
在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数P。并将P对1000000007取模的结果输出。 即输出P mod 1000000007
示例:
输入:[1,2,3,4,0]
返回值:4
其中的逆序对为:(1,0),(2,0),(3,0),(4,0)
2.思路图解
相当于归并排序,在合并有序元素的时候统计逆序对的个数。
3.代码
//记录逆序对的次数
int count = 0;
public int InversePairs(int [] array) {
if(array.length<2) return 0;
int[] temp = new int[array.length];
mergeSort(array, 0, array.length, temp);
return count;
}
//归并排序
public void mergeSort(int[] arr, int left, int right, int[] temp) {
if(right-left>1) {
int mid = (right+left)/2;
mergeSort(arr, left, mid, temp);
mergeSort(arr, mid, right, temp);
mergeData(arr, left, mid, right, temp);
System.arraycopy(temp,left, arr, left, right-left);
}
}
public void mergeData(int[] arr, int left, int mid, int right, int[] temp){
int l = left;
int r = mid;
int t = left;
while(l<mid && r<right) {
if(arr[l]<=arr[r]) {
//说明合并时有序,没有逆序对
temp[t++] = arr[l++];
}else {
//存在逆序对
temp[t++] = arr[r++];
//统计当前逆序对的个数
count += mid-l;
count %= 1000000007;
}
}
//处理剩余元素
while(l<mid){
temp[t++] = arr[l++];
}
while(r<right) {
temp[t++] = arr[r++];
}
}
二.数组中只出现一次的两个数
1.题目
一个整型数组里除了两个数字只出现一次,其他的数字都出现了两次。请写程序找出这两个只出现一次的数字。
2.思路图解
可以使用哈希map来进行解决,这里使用二进制的的方式来进行解决。
之前做过一个类似的题,就是求数组中只出现一次的一个数,其他都出现两次,求解思路是全部数字取异或,相同的数字异或结果为0,最终0和元素自身异或为自身,返回。
通过这种思路,我们可以想到将这两个不同的元素分开变为两组,这两组自身进行异或,最终结果为所求。
如何进行分组呢,由于这两个元素不相同,肯定有一位二进制位不相等,我们通过求得这个不同的二进制位来进行分组;首先所有元素进行异或,最终结果为这两个数的异或结果,然后该数与1按位与,求得不同的位置,之后所有元素与求得不同的位置与,通过是否为0来分成2组进行异或操作,最终求得的两个结果就是这两个数。
3.代码
public int[] FindNumsAppearOnce (int[] array) {
// write code here
//根据两个数的不同二进位将元素划分为两组
int tmp = 0;
for(int i=0; i<array.length; i++) tmp ^= array[i];
int mask = 1;
while((mask&tmp)==0) mask <<=1;
int a = 0;
int b = 0;
for(int i=0; i<array.length; i++) {
if((array[i]&mask)==0) {
a ^= array[i];
}else {
b ^= array[i];
}
}
if(a>b) {
int num = a;
a = b;
b = num;
}
return new int[] {a,b};
}
三.三数之和
1.题目
给出一个有n个元素的数组S,S中是否有元素a,b,c满足a+b+c=0?找出数组S中所有满足条件的三元组。
示例:
输入:[-2,0,1,1,2]
返回值:[[-2,0,2],[-2,1,1]]
2.思路图解
首先对数组进行排序,然后固定一个数为num[i],然后从后面剩元素中使用双指针,依次从当前数的下一个left 和最后一个数 right开始,如果num[left]+num[right]=-num[i],说明找到三元组,然后左右指针同时向中间移动,直到left>right结束;如果num[left]+num[right]>-num[i],说明需要右指针左移,否则左指针右移。
3.代码
public ArrayList<ArrayList<Integer>> threeSum(int[] num) {
Arrays.sort(num);
ArrayList<ArrayList<Integer>> res = new ArrayList<>();
int n = num.length;
for(int i=0; i<n-2; i++) {
if(i!=0 && num[i]==num[i-1]) continue;
int target = -num[i];
int left = i+1, right = n-1;
while(left<right) {
int tmp = num[left]+num[right];
if(tmp==target) {
ArrayList<Integer> list = new ArrayList<>();
list.add(num[i]);
list.add(num[left]);
list.add(num[right]);
res.add(list);
//去重
while(left+1<right && num[left]==num[left+1]) left++;
while(right-1>left && num[right]==num[right-1]) right--;
left++;
right--;
}else if(tmp>target) {
//说明大了,小左所有
right--;
} else {
left++;
}
}
}
return res;
}
四.N皇后问题
1.题目
n 皇后问题 研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并且使皇后彼此之间不能相互攻击。
给你一个整数 n ,返回所有不同的 n 皇后问题 的解决方案。
每一种解法包含一个不同的 n 皇后问题 的棋子放置方案,该方案中 'Q' 和 '.' 分别代表了皇后和空位。
示例:
来源:力扣(LeetCode)
链接:https://leetcode.cn/problems/n-queens
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
2.思路图解
在某个位置下象棋时,首先需要保证当前位置的左上,上,右上,左,右,左下,下,右下八个方向不能有棋子;又因为每一行只能有一个,所以不用考虑左和右,且在处理当前行的时候不需要考虑下一行的元素(因为还没有放棋子);最终只需要处理左上,上,右上三个位置进行判断。
使用递归回溯的方式处理每一行,在判断当前位置是否可以放旗子先判断三个位置,如果处理到了最后一层,就保存当前棋盘信息。如果递归处理完当前位置的元素,之后回溯的时候还需要修改当前位置为未放棋子的初始状态。最终处理完第一行所有可能出现的结果即可。
3.代码
List<List<String>> res = new ArrayList<>();
public List<List<String>> solveNQueens(int n) {
char[][] board = new char[n][n];
for(char[] c:board)
Arrays.fill(c, '.');
dfs(board, 0);
return res;
}
//递归回溯来寻找每个位置放置皇后是否符合结果,一行放一个,判断一下是否满足皇后要求
public void dfs(char[][] board, int row) {
if(row==board.length) {
res.add(charToList(board));
}
for(int i=0; i<board[0].length; i++) {
if(!isValid(board, row, i)) continue;
board[row][i] = 'Q';
dfs(board, row+1);
board[row][i] = '.';
}
}
//判断是否冲突
public boolean isValid(char[][] board, int row, int col) {
//分别检查上,上左,上右三个方向
//上
for(int i=0; i<board.length; i++) {
//说明冲突
if(board[i][col]=='Q') return false;
}
//上右
for(int i=row-1,j=col+1; i>=0&&j<board[0].length; i--, j++) {
if(board[i][j]=='Q') return false;//冲突
}
//上左
for(int i=row-1,j=col-1; i>=0&&j>=0; i--, j--) {
if(board[i][j]=='Q') return false;
}
return true;
}
//将符合皇后的二维矩阵保存起来
public List<String> charToList(char[][] board){
List<String> list = new ArrayList<>();
for(char[] c:board) {
list.add(new String(c));
}
return list;
}
五.矩阵最长递增路径
1.题目
给定一个 n 行 m 列矩阵 matrix ,矩阵内所有数均为非负整数。 你需要在矩阵中找到一条最长路径,使这条路径上的元素是递增的。并输出这条最长路径的长度。
这个路径必须满足以下条件:
1. 对于每个单元格,你可以往上,下,左,右四个方向移动。 你不能在对角线方向上移动或移动到边界外。
2. 你不能走重复的单元格。即每个格子最多只能走一次。
例如:当输入为[[1,2,3],[4,5,6],[7,8,9]]时,对应的输出为5,
其中的一条最长递增路径如下图所示:
2.思路图解
首先以每一个结点作为初始递增位置进行寻找,在寻找的过程中保存前一个节点的元素,和当前元素比较是否满足递增,之后在满足不超过边界的情况下进行从4个方向进行递归寻找最大递归路径,在寻找的过程中,如果当前位置元素的路径已经找过,那么就直接返回当前位置起始的最大路径(优化),最终返回最大路径即可。
3.代码
public int solve (int[][] matrix) {
// write code here
//记录从当前位置的路径已经寻找过了
int[][] dp = new int[matrix.length][matrix[0].length];
int max = 0;
for(int i=0; i<dp.length; i++) {
for(int j=0; j<dp[0].length; j++) {
max = Math.max(max, dfs(matrix, i, j, dp, -1));
}
}
return max;
}
public int dfs(int[][] matrix, int i, int j, int[][] dp, int pre) {
//判断是否递增
if(pre>=matrix[i][j]) return 0;
//说明从当前位置开始的最长路径已经存在,不用再寻找,直接返回
if(dp[i][j]!=0)
return dp[i][j];
int max = 0;
//分别从4个方向寻找递增路径
if(i<matrix.length-1) {
max = Math.max(max,dfs(matrix, i+1, j, dp, matrix[i][j]));
}
if(i>0) {
max = Math.max(max, dfs(matrix, i-1, j, dp, matrix[i][j]));
}
if(j<matrix[0].length-1) {
max = Math.max(max, dfs(matrix, i, j+1, dp, matrix[i][j]));
}
if(j>0) {
max = Math.max(max, dfs(matrix, i, j-1, dp, matrix[i][j]));
}
//说明已经最长路径已经寻找结束,加上当前位置元素作为路径中的一步
dp[i][j] = max+1;
return max+1;
}
六.最小花费爬楼梯
1.题目
给定一个整数数组 cost ,其中 cost[i]] 是从楼梯第i个台阶向上爬需要支付的费用,下标从0开始。一旦你支付此费用,即可选择向上爬一个或者两个台阶。
输入:[2,5,20]
返回值:5
说明:你将从下标为1的台阶开始,支付5 ,向上爬两个台阶,到达楼梯顶部。总花费为5
2.思路图解
使用dp数组可以保留前两个台阶处的最小花费,因为当前台阶可能是从前一个台阶或前两个台阶到达,所以在计算当前位置的时候需要取前两个位置到当前位置的最小值;直到越过最后一个台阶即可。
3.代码
public int minCostClimbingStairs (int[] cost) {
//多一个大小是因为最后一个台阶要越过
int[] dp = new int[cost.length+1];
//已经默认初始化前两个台阶为0
for(int i=2; i<dp.length; i++) {
//每次看从前一个还是前两个到当前台阶
dp[i] = Math.min(dp[i-2]+cost[i-2], dp[i-1]+cost[i-1]);
}
return dp[cost.length];
}
七.最长无重复子数组
1.题目
给定一个长度为n的数组arr,返回arr的最长无重复元素子数组的长度,无重复指的是所有数字都不相同。
子数组是连续的,比如[1,3,5,7,9]的子数组有[1,3],[3,5,7]等等,但是[1,3,7]不是子数组。
示例:
arr=[2,2,3,4,2] 最长无重复子数组为[2,3,4],长度为3.
2.思路图解
使用map来记录元素出现的位置,其中key为元素值,value为元素下标,之后每遇到一个元素,就判断map中是否出现该元素,如果出现,就修改初始位置begin为重复元素下标的下一个;然后将该元素和下标存放在map中,并计算保存无重复数组的最大长度。
3.代码
public int maxLength (int[] arr) {
// write code here
int len = 1;
int beign = 0;
Map<Integer, Integer> map = new HashMap<>();
map.put(arr[0],0);
for(int i=1; i<arr.length; i++) {
if(map.containsKey(arr[i])) {
//每次找到重复位置的下一个元素位置作为beign的开始
beign = Math.max(beign, map.get(arr[i])+1);
}
map.put(arr[i], i);
//计算无重复的最长子数组
len = Math.max(len,i-beign+1);
}
return len;
}
八.分割等和子集
1.题目
给定一个非空的正整数数组 nums
,请判断能否将这些数字分成元素和相等的两部分。
示例:
arr=[1,5,11,5], 可以将数组分为[1,5,5,] 和[11]两部分。
2.思路图解
先求出所有元素的和,然后判断是否为偶数,如果不是,说明分割不了,如果是偶数,接下来就进行分割。
首先借助二维dp数组,其中dp[i][j]代表从前i个元素是否可以存放满j的空间,之后在寻找dp[i][j]有两种状态,一种是前i-1个元素已经足够j空间的大小;另一种是前i-1还没有存满,j的空间还可以存放
,接下来存放j-nums[i-1]的数量。
3.代码
public boolean canPartition(int[] nums) {
int sum = 0;
for(int num:nums) sum+=num;
if(sum%2!=0) return false;
boolean[][] bag = new boolean[nums.length+1][sum/2+1];
bag[0][0] = true;
for(int i=1; i<=nums.length; i++) {
for(int j=0; j<=sum/2; j++) {
//先记录前一个i-1的状态,如果已经满了,之后就不用再放i-1的东西了
bag[i][j] = bag[i-1][j];
if(!bag[i][j] && j>=nums[i-1]) {
//说明当前可以选择放i-1位置的东西
bag[i][j] = bag[i-1][j-nums[i-1]];
}
}
}
return bag[nums.length][sum/2];
}
九.节点间通路
1.题目
节点间通路。给定有向图,设计一个算法,找出两个节点之间是否存在一条路径。
输入:n = 5, graph = [[0, 1], [0, 2], [0, 4], [0, 4], [0, 1], [1, 3], [1, 4], [1, 3], [2, 3], [3, 4]], start = 0, target = 4 输出 true
2.图解思路
3.代码实现
Set<Integer>[] arr;
boolean[] isVi;//记录当前结点是否处理过
public boolean findWhetherExistsPath(int n, int[][] graph, int start, int target) {
//初始化arr数组大小
arr = new Set[n];
for(int i=0; i<n; i++) {
//给每一个数组位置初始化一个set集合
arr[i] = new HashSet<Integer>();
}
//将每个对应的start插入到指定的set中
for(int[] a: graph) {
if(a[0]!=a[1]) {
//说明不是和自身重合,添加到arr中
arr[a[0]].add(a[1]);
}
}
isVi = new boolean[n];
//初始化结束后,开始深度搜索是否存在结点通路
return dfs(start, target);
}
public boolean dfs(int beign, int end) {
//将当前初始点设置为访问过状态
isVi[beign] = true;
//如果结束点没有访问过,继续寻找
if(!isVi[end]) {
//遍历set集合其他节点
Set<Integer> s = arr[beign];
for(int num: s) {
//当前结点未访问则递归继续寻找
if(!isVi[num] && dfs(num, end)) {
return true;//说明找到
}
}
}
return isVi[end];
}