数组习题总结4

目录

一.求数组中的逆序对

1.题目

2.思路图解

3.代码

二.数组中只出现一次的两个数

1.题目

2.思路图解

3.代码

三.三数之和

1.题目

2.思路图解

3.代码

四.N皇后问题

1.题目

2.思路图解

3.代码

五.矩阵最长递增路径

1.题目

2.思路图解

3.代码

六.最小花费爬楼梯

1.题目

2.思路图解

3.代码

七.最长无重复子数组

1.题目

2.思路图解

3.代码

八.分割等和子集

1.题目

2.思路图解

3.代码

九.节点间通路

1.题目

2.图解思路

3.代码实现


一.求数组中的逆序对

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];
    }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值