目录
1.数组和问题
1.1 两个数之和为k
这种题目比较简单,如果要返回的是具体的数,那么可以直接排序然后利用二分查找来做,如果要返回的是数组的下标,那么可以用Map存储数组值和下标。
public int[] twoSum2(int[] nums, int target) {
Map<Integer, Integer> map = new HashMap<>();
for (int i = 0; i < nums.length; i++) {
int complement = target - nums[i];
if (map.containsKey(complement)) {
return new int[] { map.get(complement), i };
}
map.put(nums[i], i);
}
return new int[] {};
}
1.2 三数之和为0
同样可以参考两数之和的解法,可以先固定一个数,然后利用二分查找的方法来查找,这里我们要特别注意的一个问题就是去重问题,由于这里是返回的值,可以对数组先排序,其具体代码如下
public static List<List<Integer>> threeSum(int[] nums) {
List<List<Integer>> result = new ArrayList<>();
if (nums != null && nums.length > 2) {
// 先对数组进行排序
Arrays.sort(nums);
// i表示假设取第i个数作为结果
for (int i = 0; i < nums.length - 2; i++) {
if (i != 0 && nums[i] == nums[i-1]) continue;
int j = i + 1;
int k = nums.length - 1;
while (j < k) {
// 如果找到满足条件的解
if (nums[j] + nums[k] == -nums[i]) {
// 将结果添加到结果含集中
List<Integer> list = new ArrayList<>(3);
list.add(nums[i]);
list.add(nums[j]);
list.add(nums[k]);
result.add(list);
// 移动到下一个位置,找下一组解
k--;
j++;
// 从左向右找第一个与之前处理的数不同的数的下标
while (j < k && nums[j] == nums[j - 1]) {
j++;
}
// 从右向左找第一个与之前处理的数不同的数的下标
while (j < k && nums[k] == nums[k + 1]) {
k--;
}
}
// 和大于0
else if (nums[j] + nums[k] > -nums[i]) {
k--;
}
// 和小于0
else {
j++;
}
}
}
}
return result;
}
1.3 最接近的三数之和
public static int threeSumClosest(int[] nums, int target) {
Arrays.sort(nums);
int min=Integer.MAX_VALUE;
int closeSum=0;
for (int i = 0; i < nums.length - 2; i++) {
int j = i + 1;
int k = nums.length - 1;
while (j < k) {
int curSum=nums[j] + nums[k]+nums[i];
int temp=Math.abs(curSum-target);
// 如果找到满足条件的解
if (temp<min) {
closeSum=curSum;
min=temp;
}else if(curSum>target){
k--;
}else if(curSum<target){
j++;
}else {
return curSum;
}
}
}
return closeSum;
}
1.4 四个数之和
/*
* 同样可以固定两个数,然后剩下的利用双指针来解决
*/
public static List<List<Integer>> fourSum(int[] nums, int target) {
List<List<Integer>> result = new ArrayList<>();
if(nums.length==0||nums==null) {
return result;
}
Arrays.sort(nums);
for(int i=0;i<nums.length-3;i++) {
if (i != 0 && nums[i] == nums[i-1]) continue;
for(int j=i+1;j<nums.length-2;j++) {
if (j != i+1 && nums[j] == nums[j-1]) continue;
int curSum=nums[i]+nums[j];
int low=j+1;
int high=nums.length-1;
while(low<high) {
if(nums[low]+nums[high]==target-curSum) {
List<Integer> list = new ArrayList<>(4);
list.add(nums[i]);
list.add(nums[j]);
list.add(nums[low]);
list.add(nums[high]);
result.add(list);
low++;
high--;
//去重
while(low<high&&nums[low]==nums[low-1]) {
low++;
}
while(low<high&&nums[high]==nums[high+1]) {
high--;
}
}else if(nums[low]+nums[high]>target-curSum) {
high--;
}else {
low++;
}
}
/*while(j<nums.length-2&&nums[j]==nums[j+1]) {
j++;
}*/
}
/*while(i<nums.length-3&&nums[i]==nums[i+1]) {
i++;
}*/
}
return result;
}
1.5 和为k的子数组问题I
给定一个无重复元素的数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。
candidates 中的数字可以无限制重复被选取。
//利用回溯算法
public static List<List<Integer>> combinationSum(int[] candidates, int target) {
List<List<Integer>> lists=new ArrayList<>();
if(candidates.length==0) {
return lists;
}
dfs(candidates,0,new ArrayList<>(),lists,target);
return lists;
}
private static void dfs(int[] candidates, int index, List<Integer> list, List<List<Integer>> lists, int target) {
// TODO Auto-generated method stub
//满足条件退回到上一步
if(target==0) {
lists.add(new ArrayList<>(list));
return;
}
if(target<0) {
return;
}
for(int i=index;i<candidates.length;i++) {
list.add(candidates[i]);
dfs(candidates,i,list,lists,target-candidates[i]);
list.remove(list.size()-1);
}
}
1.6 和为k的子数组问题I
数组中每一个数字只能使用一次
//利用回溯算法
public static List<List<Integer>> combinationSum(int[] candidates, int target) {
List<List<Integer>> lists=new ArrayList<>();
if(candidates.length==0) {
return lists;
}
Arrays.sort(candidates);
dfs(candidates,0,new ArrayList<>(),lists,target);
return lists;
}
private static void dfs(int[] candidates, int index, List<Integer> list, List<List<Integer>> lists, int target) {
// TODO Auto-generated method stub
//满足条件退回到上一步
if(target==0) {
if(!lists.contains(list))
lists.add(new ArrayList<>(list));
return;
}
if(target<0) {
return;
}
for(int i=index;i<candidates.length;i++) {
list.add(candidates[i]);
dfs(candidates,i+1,list,lists,target-candidates[i]);
list.remove(list.size()-1);
}
}
1.7 和为k的子数组问题III
找出所有相加之和为 n 的 k 个数的组合。组合中只允许含有 1 - 9 的正整数,并且每种组合中不存在重复的数字。
public static List<List<Integer>> combinationSum3(int k, int n) {
List<List<Integer>> lists=new ArrayList<>();
if(k==0||n==0)
return lists;
dfs(k,n,1,lists,new ArrayList<>());
return lists;
}
private static void dfs(int k,int target, int index, List<List<Integer>> lists, List<Integer> list) {
// TODO Auto-generated method stub
if(list.size()==k&&target==0) {
if(!lists.contains(list))
lists.add(new ArrayList<>(list));
return;
}
if(target<0) {
return ;
}
for(int i=index;i<=9;i++) {
list.add(i);
dfs(k,target-i,i+1,lists,list);
list.remove(list.size()-1);
}
}
1.8 数字和为Sum的方法数
输入描述:
输入为两行: 第一行为两个正整数n(1 ≤ n ≤ 1000),sum(1 ≤ sum ≤ 1000) 第二行为n个正整数A[i](32位整数),以空格隔开。
输出描述:
输出所求的方案数
示例1
输入
5 15
5 5 10 2 3
输出
4
刚开始看到这个题的时候,第一眼就想到用递归来做,用回溯来做,说实话递归确实好写,但复杂度特别高为O(N^N),测试了一下,确实通不过
后来在评论区里看到全都是用dp动态规划来做的,分析了一下,这个有点类似于0-1背包问题,就是要么取这个数,要么不取这个数,将这两种方案数加起来就是总的方案数,0-1背包问题的公公式如下:
设dp[i][j]表示前i个数字中和为j个组合数(下标从1开始),则递推公式为:
初始条件:dp[i][0]=1,i=0,1,2,…,ndp[i][0]=1,i=0,1,2,…,n.
初始条件是指,如果和为0,那么一个也不选,不选也是一种选择,所以是1.
以本题中的例子为例,借用评论区里面的一张示意图,得到的动态规划矩阵如下:
这里由于要对应下标,所以所有的小下标都是从1开始的,先看第一列,表示的是取前i个数能组成sum为0的方法数,只有一种,就是什么都不取,再来看第一行(除dp[0][0]以外)表示的是取前0个数能组成sum为0~15的方法数,当然全部是0(即这种是不可能的),这些位置的值初始化完成之后,开始从第二行第二列遍历数组,其核心公式为dp[i][j]=dp[i-1][j]+dp[i-1][j-num[i]];以dp[5][5]为例来说吧,图中dp[5][5]=3,
1.假设不取.num[5]=3这个数,那么利用前5个数组成和为5的方案有多少种呢?就是前4个数组成和为5的方案数,也就是dp[4][5]的值,dp[4][5]=2;
2.假设取num[5]=3这个数,那么利用前5个数组成和为5的方案有多少种呢?我们只需要找到利用前5个数组成和为2的方案数就可以了,加上这个3肯定就是组成sum为5了,而dp[5][2]=1。将这两种方案加起来就是3种了。
代码如下:
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
int sum = sc.nextInt();
int[] num = new int[n+1];
for (int i = 1; i <=n; i++) {
num[i] = sc.nextInt();
}
long[][] dp=new long[n+1][sum+1];
for(int i=0;i<n;i++){
dp[i][0]=1; //表示取前i个数字组成和为0的方法数为1(就是什么都不取)
}
for(int j=0;j<sum;j++){
dp[0][j]=0; //表示取0个数组成sum的方法数为0(就是这是不可能的)
}
for(int i=1;i<=n;i++){
for(int j=1;j<=sum;j++){
if(j>=num[i]){
dp[i][j]=dp[i-1][j]+dp[i-1][j-num[i]];
}else{
dp[i][j]=dp[i-1][j];
}
}
}
System.out.print(dp[n][sum]);
}
1.9 最大子数组和问题
public class MaxSubArray{
/*
利用动态规划去做
*/
public int maxSubArray(int[] nums){
int[] dp=new int[nums.length];
dp[0]=nums[0];
for(int i=0;i<nums.length-1;i++){
dp[i+1]=Math.max(dp[i]+nums[i+1],nums[i+1]);
}
//遍历dp[]找到最大值
int max=dp[0];
for(int i=0;i<dp.length;i++){
if(dp[i]>max){
max=dp[i];
}
}
return max;
}
public static void main(String[] args) {
MaxSubArray msa=new MaxSubArray();
int[] nums=new int[]{-2,1,-3,4,-1,2,1,-5,4};
System.out.print(msa.maxSubArray(nums));
}
}
2.其他数组问题
2.1 电话号码的组合
public static List<String> letterCombinations(String digits) {
List<String> list=new ArrayList<>();
String[] str=new String[] {" "," ","abc","def","ghi","jkl","mno","pqrs","tuv","wxyz"};
if(digits.length()==0||digits==null) {
return list;
}
dfs(digits,str,0,list,new StringBuffer());
return list;
}
private static void dfs(String digits, String[] str, int index, List<String> list,StringBuffer res) {
// TODO Auto-generated method stub
if(index==digits.length()) {
list.add(res.toString());
return;
}
String phone=str[digits.charAt(index)-'0'];
for(int i=0;i<phone.length();i++) {
res.append(phone.charAt(i));
dfs(digits,str,index+1,list,res);
res.deleteCharAt(res.length()-1);
}
}
2.2 跳跃问题
public class CanJump{
public static boolean canJump(int[] nums){
if(nums.length==1){
return true;
}
//表示从i位置出发能到达的最远位置
int far=0;
for(int i=0;i<nums.length;i++){
//返回false的情况
if(far<i){
return false;
}
far=Math.max(nums[i]+i,far);
if(far>=nums.length-1){
return true;
}
}
return true;
}
}
2.3 合并两个有序数组
题目描述
给定两个有序整数数组 nums1 和 nums2,将 nums2 合并到 nums1 中,使得 num1 成为一个有序数组。
说明:
- 初始化 nums1 和 nums2 的元素数量分别为 m 和 n。
- 你可以假设 nums1 有足够的空间(空间大小大于或等于 m + n)来保存 nums2 中的元素。
示例:
输入: nums1 = [1,2,3,0,0,0], m = 3 nums2 = [2,5,6], n = 3 输出: [1,2,2,3,5,6]
public class Merge{
/*
一开始准备从前往后比较,发现不合适,然后参考了一下可以考虑从后往前比较
*/
public void merge(int[] nums1, int m, int[] nums2, int n) {
int len=m+n-1; //表示总的元素个数
if(m<1){
while(len>=0){
nums1[len--]=nums2[--n];
}
}
int index1=m-1;
int index2=n-1;
while(index1>=0&&index2>=0){
if(nums1[index1]>nums2[index2]){
nums1[len--]=nums1[index1--];
}
else{
nums1[len--]=nums2[index2--];
}
}
while(index1>=0){
nums1[len--]=nums1[index1--];
}
while(index2>=0){
nums1[len--]=nums2[index2--];
}
}
}