【Day10】每天三算法

本文探讨了如何通过动态规划和二分法优化解决NC91最长上升子序列问题,同时介绍了如何利用二分法求解NC32求平方根,以及如何使用回溯法处理NC121字符串排列。通过实例和优化策略,提升算法效率并理解字典序最小原则。
摘要由CSDN通过智能技术生成

【Day10】每天三算法

题目

NC91 最长上升子序列(三)

描述

给定数组 arr ,设长度为 n ,输出 arr 的最长上升子序列。(如果有多个答案,请输出其中 按数值(注:区别于按单个字符的ASCII码值)进行比较的 字典序最小的那个)

示例1

输入:

[2,1,5,3,6,4,8,9,7]

返回值:

[1,3,4,8,9]

示例2

输入:

[1,2,8,6,4]

返回值:

[1,2,4]

说明:

其最长递增子序列有3个,(1,2,8)、(1,2,6)、(1,2,4)其中第三个 按数值进行比较的字典序 最小,故答案为(1,2,4)

思考

最长递增子串这道题我们之前做过,使用动态规划即可轻松得出答案

但是对于这道题,有三个问题

**1、**我们要获取这个子串,而不是最长递增子串的长度

**2、**要想办法获取字典序最小的那个

对与问题1、2,我们可以在 dp 数组上动手脚,在dp[i]计算完成后,为了得到最长递增子序列,需要从后往前对dp数组进行一次遍历,找到符合条件的数填入答案中。由于 dp[i] 的值绝对能在左侧找到更小值 dp[i] ,故只需要选取第一次遇见的满足条件的数即可

public class NC91_LIS {
    public static void main(String[] args) {
        int[] arr = {2,1,5,3,6,4,8,9,7};
        NC91_LIS solu = new NC91_LIS();
        System.out.println(Arrays.toString(solu.LIS(arr)));
    }

    // 方法1:动态规划
    public int[] LIS (int[] arr) {
        int[] dp = new int[arr.length];
        for (int i = 0; i < dp.length; i++) {
            dp[i]=1;
        }
        // 获取最长递增子序列的 dp 数组
        int maxLen = Integer.MIN_VALUE;
        for (int i = 1; i < arr.length; i++) {
            int preMax = 0;
            for (int j = 0; j < i; j++) {
                if (arr[j]<=arr[i]) preMax=Math.max(preMax,dp[j]);
            }
            dp[i]=preMax+1;
            maxLen=Math.max(maxLen,dp[i]);
        }

      	// 借助 dp 数组获取最长上升子序列
        int[] res = new int[maxLen];
        for (int i = dp.length-1,index = maxLen-1; index >=0 ; i--) {
            if (dp[i]==maxLen) {
                res[index--]=arr[i];
                maxLen--;
            }
        }
        return res;
    }
}

熟悉我的读者知道,一旦我的题解没有写在题解里,说明这个解法跑不通,事实也确实如此,这里动规的时间复杂度为 O(n^2),会超时

所以我才要介绍下面使用二分法和贪心优化的方法:

这里我直接借助力扣上的思路

贪心+二分思路

题解

二分的写法这里就作为作业,交个各位课后去做吧

NC32 求平方根

描述

求出一个数的平方根(如果是小数的话,只需要取整数位即可)

思考

方法一:暴力解法

这是最容易想到的解法,遍历从1-n的数,取其平方并与目标数比较即可

但是这个解法的问题也十分明显

1)时间复杂度相对较高(其实时间复杂度为 n,不是很高,但是与我们接下来要介绍的方法相比,就比较逊色了)

2)平方计算可能会溢出

方法二:二分法

其实就是暴力解法的改版

但是一样要注意溢出问题

这里可以运用除法,处理二分:

while (l<=r) {
  int mid = (l + r) / 2;
  if (mid<=x/mid && (mid+1)>x/(mid+1)) return mid;
  if (mid>x/mid) r=mid-1;
  else l=mid+1;
}

当然,如果这个写法一下子写不起来的,可以先写乘法溢出的版本,然后再调换成除法的版本

题解
import java.util.*;


public class Solution {
    /**
     * 
     * @param x int整型 
     * @return int整型
     */
    public int sqrt (int x) {
        if (x<=1) return x;
        int l=0,r=x;
        while (l<=r) {
            int mid = (l + r) / 2;
            if (mid<=x/mid && (mid+1)>x/(mid+1)) return mid;
            if (mid>x/mid) r=mid-1;
            else l=mid+1;
        }
        return -1;
    }
}

NC121 字符串的排列

描述

输入一个长度为 n 字符串,打印出该字符串中字符的所有排列,你可以以任意顺序返回这个字符串数组。

例如输入字符串ABC,则输出由字符A,B,C所能排列出来的所有字符串ABC,ACB,BAC,BCA,CBA和CAB。

示例

思考

这道题本身就是经典的回溯问题,由于篇幅限制,这里对回溯的思想就不做过多的讲解了,想要了解的同学可以自行搜索

题解
import java.util.ArrayList;
import java.util.HashSet;
public class Solution {
    public ArrayList<String> Permutation(String str) {
        ArrayList<String> res = new ArrayList<>();
        boolean[] visited = new boolean[str.length()];
        backTrack(str,res,new StringBuilder(),visited);
        return res;
    }

    private void backTrack(String str,ArrayList<String> res,StringBuilder builder,boolean[] visited) {
        // 递归终止条件
        if (builder.toString().length()==str.length()) {
            res.add(builder.toString());
            return;
        }
        // 存储已经用过的 char,防止出现重复的字符
        HashSet<Character> usedCharSet = new HashSet<>();
        for (int i = 0; i < visited.length; i++) {
            if (!visited[i] && !usedCharSet.contains(str.charAt(i))) {
                usedCharSet.add(str.charAt(i));
                builder.append(str.charAt(i));
                visited[i]=true;
                backTrack(str,res,builder,visited);
                // 回溯
                builder.replace(builder.length()-1,builder.length(),"");
                visited[i]=false;
            }
        }
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

FARO_Z

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值