算法小题分析

最大字段和 

给定由 n 个整数(可能为负整数)组成的序列,求解其连续的最大字段和。当所有数都是负整数时,最大字段和是 0 .
如:a[] = {-2, 11, -4, 13, -5, -2}时, max = 11 + (-4) + 13 = 20.

扫描数组并且进行加和,设置一个和数组sum[i],表示数组a前i项的和,最终求出sum数组的最大值即可

public static int MaxArray(int []list){
        int len = list.length;
        int []sum = new int[len];//标记数组
        sum[0] = list[0];
        for (int i = 1;i < len;i++){
            sum[i] = list[i] + sum[i-1];//每次更新字段和
        }
        //求sum数组的最大值
        int max = -999;
        boolean flag = false;
        for (int i = 0;i < len;i++){//字段和的最大值
            if (max < sum[i]){
                max = sum[i];
            }
            if (sum[i]>0) flag =true;//特殊情况的判断
        }
        if (flag) return max;
        return 0;
    }

牢房问题

第一次改变:

1 2 3 4 5 6 7 8 9....门的状态

第二次改变:

   2    4    6    8

第三次改变:

      3       6       9

也就是说第一次改变1的倍数号门,第二次改变2的倍数号门,以此类推,最终找到门的状态改变次数为奇数的即可

最终转换为1-n中各个数因数的个数问题

import java.util.Scanner;

public class demo1 {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        int n = scanner.nextInt();
        int arr[] = new int[n + 1];
        for (int i = 0;i <= n;i++){
            arr[i] = 0;
        }
        for (int i = 1;i <=n;i++){
            for (int j = 1;j<=n;j++){
                if (j % i == 0){
                    arr[j]++;
                }
            }
        }
        for (int i = 1;i <= n;i++){
            if (arr[i] % 2 != 0)
            System.out.println(i);
        }

    }

}

不同的子序列

给定一个字符串 s,计算 s 的 不同非空子序列 的个数。因为结果可能很大,所以返回答案需要对 10^9 + 7 取余 。

字符串的 子序列 是经由原字符串删除一些(也可能不删除)字符但不改变剩余字符相对位置的一个新字符串。

例如,"ace" 是 "abcde" 的一个子序列,但 "aec" 不是。

方法一:

d[i]表示前i个字符的子集个数(包含空集)

newadd表示d[i]相较于d[i-1]新增的子集

renewadd表示newadd中与已存在的子集重复的部分

 由此可得,newadd=d[i-1]

每次新增字符x新增的重复的子集个数等于字符x上一次出现新增的子集个数

public static int distinctSubseqII(String s){
        int mod = (int) 1e9 + 7;//防止数值过大越界,题目要求
        int n = s.length();
        int []d = new int[n];//d[i]表示,字符s[i]前字符串的子集个数(包括空集)
        int []renewadd = new int[26];
        renewadd[(int)(s.charAt(0)-'a')]=1;
        //初始值为1,在空集上新增一个
        d[0] = 2;//前一个字符的字符串的子集个数
        for (int i = 1;i<n;i++){
            int newadd = d[i-1];//新增的值等于d[i-1]
            d[i] = ((d[i-1] + newadd)% mod  - renewadd[(int)(s.charAt(i)-'a')]% mod + mod)% mod;
            renewadd[(int)(s.charAt(i)-'a')] = newadd;//记录字符s[i]这次出现新增的子集个数
        }
        return d[n-1] - 1;//去掉空集
    }

方法二:

dp[i]表示以字符s[i]结尾的子集的个数

当字符串每个字母都不相同时,任何一个子序列再加上一个字母都会得到一个新的子序列,所以我们在求当前字母结尾的子序列个数时,就等于前面每个字母结尾的子序列的个数之和再加1,即dp[i] = dp[0] + dp[1] + dp[2] +...+ dp[i - 1] + 1,这里的+1是指子序列只包含当前字母本身。

但字符串中会有相同字母,比如s = "abac", 在下标0和下标2位置都是以a结尾,dp[0]包含"a"这一种情况dp[2]包含"a", "aa", "ba", "aba"这四种情况,事实上dp[2]已经包含了dp[0]这种情况(dp[2] = dp[0] + dp[1] + 1),所以我们只要累计某个字母最后一次出现的位置上的dp值,即dp[3] = dp[1] + dp[2] + 1

public static int distinctSubseqII(String s){
        final long MOD = 1000000007;
        int len = s.length();
        int[] last = new int[26]; //记录某个字母最后一次出现的下标
        Arrays.fill(last, -1);
        long[] dp = new long[len];//dp[i]表示由前i个字符组成的字符串中字符s[i]结尾的子集的个数
        for(int i = 0; i < len; i++){//此循环逐渐包含整个字符串,使last[j]逐渐更新
            long sum = 1; // 这里的1是指当子序列只包含当前字母本身时

            for(int j = 0; j < 26; j++){//
                if(last[j] != -1){
                    //dp[last[j]]表示由前last[j](角标)个字符组成的字符串中以字母j最后一次出现作为末尾字符的子集个数
                    sum = (sum + dp[last[j]]) % MOD;
                    //sum为各个字母最后一次出现作为末尾字符的子集个数之和
                }
            }
            last[s.charAt(i) - 'a'] = i; // 更新为最后出现时的下标
            dp[i] = sum;//表示由前i个字符组成的字符串中字符s[i]结尾的子集的个数
        }
        long res = 0;
        for(int i = 0; i < 26; i++){//所有字母最后出现且以该字母为结尾的子集个数和则为最终结果
            if(last[i] != -1){
                res = (res + dp[last[i]]) % MOD;
            }
        }
        
        return (int)res;

    }

用栈操作构建数组

给你一个数组 target 和一个整数 n。每次迭代,需要从  list = { 1 , 2 , 3 ..., n } 中依次读取一个数字。

请使用下述操作来构建目标数组 target :

"Push":从 list 中读取一个新元素, 并将其推入数组中。
"Pop":删除数组中的最后一个元素。
如果目标数组构建完成,就停止读取更多元素。
题目数据保证目标数组严格递增,并且只包含 1 到 n 之间的数字。

请返回构建目标数组所用的操作序列。如果存在多个可行方案,返回任一即可。

分析:

依次遍历1-n,

若与target匹配成功,则只输出“Push”,只放不出

若与target匹配失败,则输出“Push”"Pop",先放再出

依次将命令存入数组,输出即可

public static List<String> buildArray(int[] target, int n){
        int len = target.length;
        List<String> build = new LinkedList<>();
        int j = 0;
        for (int i=1;i<=n;i++){
            build.add("Push");
            if (target[j]!=i) build.add("Pop");
            else j++;
            if (j==len)break;
        }
        return build;
    }

合并区间 

以数组 intervals 表示若干个区间的集合,其中单个区间为 intervals[i] = [starti, endi] 。请你合并所有重叠的区间,并返回 一个不重叠的区间数组,该数组需恰好覆盖输入中的所有区间 。

示例 1:

输入:intervals = [[1,3],[2,6],[8,10],[15,18]]
输出:[[1,6],[8,10],[15,18]]
解释:区间 [1,3] 和 [2,6] 重叠, 将它们合并为 [1,6].

示例 2:

输入:intervals = [[1,4],[4,5]]
输出:[[1,5]]
解释:区间 [1,4] 和 [4,5] 可被视为重叠区间。

public static int[][] merge(int[][] intervals) {
        //排序
        for (int i = 0; i < intervals.length; i++) {
            for (int k = 0; k < intervals.length - 1; k++) {
                if (intervals[k][0] > intervals[k + 1][0]) {
                    int t = intervals[k][0];
                    int m = intervals[k][1];
                    intervals[k][0] = intervals[k + 1][0];
                    intervals[k][1] = intervals[k + 1][1];
                    intervals[k + 1][0] = t;
                    intervals[k + 1][1] = m;
                }
            }
        }
        int[][] res = new int[intervals.length][2];//存结果的数组
        int idx = -1;
        for (int[] interval: intervals) {
            // 如果结果数组是空的,或者当前区间的起始位置 > 结果数组中最后区间的终止位置,
            // 则不合并,直接将当前区间加入结果数组。
            if (idx == -1 || interval[0] > res[idx][1]) {
                res[++idx] = interval;
            } else {
                // 反之将当前区间合并至结果数组的最后区间
                res[idx][1] = Math.max(res[idx][1], interval[1]);
            }
        }
        return Arrays.copyOf(res, idx + 1);
    }

对角线遍历

给你一个大小为 m x n 的矩阵 mat ,请以对角线遍历的顺序,用一个数组返回这个矩阵中的所有元素。

public static int[] findDiagonalOrder(int[][] mat) {
        int n=mat.length;//行
        int m=mat[0].length;//列
        int row;
        int col;
        int []res=new int[m*n];
        int index=0;
        for (int k=0;k<m+n-1;k++){//k表示对角线数
            if (k%2==0){
                if (k<n){
                    row=k;
                    col=0;
                }else {
                    row=n-1;
                    col=k-n+1;
                }
                while (row>=0&&col<m){
                    res[index]=mat[row][col];
                    index++;
                    row--;
                    col++;
                }
            }
            if (k%2==1){
                if (k<m){
                    row=0;
                    col=k;
                }else {
                    row=k-m+1;
                    col=m-1;
                }
                while (row<n&&col>=0){
                    res[index]=mat[row][col];
                    index++;
                    row++;
                    col--;
                }
            }
        }
        return res;
    }

最长公共前缀

编写一个函数来查找字符串数组中的最长公共前缀。如果不存在公共前缀,返回空字符串 ""。

输入:strs = ["flower","flow","flight"]
输出:"fl"
示例 2:

输入:strs = ["dog","racecar","car"]
输出:""
解释:输入不存在公共前缀。

public static String longestCommonPrefix(String[] strs) {
        ArrayList<Character> list =new ArrayList<>();
        int n=strs.length;
        //寻找字符串的最短值
        int min=Integer.MAX_VALUE;
        for (int i=0;i<n;i++){
            if (min>strs[i].length()) min= strs[i].length();
        }
        for (int i=0;i<min;i++){//每个字符遍历
            int flag=0;//只有所有的字符串的字符i都相同的标志
            for (int j=0;j<n-1;j++){//字符串遍历
                if (strs[j].charAt(i)==strs[j+1].charAt(i)) //相邻比较
                    flag++;
            }
            if (flag==n-1) list.add(strs[0].charAt(i));//所有的字符串的字符i都相同,存入数组
            else break;
        }
        if (list.size()==0)return "";
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < list.size(); i++) {
            sb.append(list.get(i));
        }
        return String.valueOf(sb);
    }

最长回文字符串

给你一个字符串 s,找到 s 中最长的回文子串。

示例 1:

输入:s = "babad"
输出:"bab"

示例 2:

输入:s = "cbbd"
输出:"bb"

方法一:中心扩展

遍历字符串的每一个字符,并以此字符为中心向两边遍历寻找相同字符,每寻找完以一个字符为中心后,更新最长序列的边界值

回文字符分为两种形式:奇数回文(例1)和偶数回文(例2)

public static String longestPalindrome(String s) {
        int l=0;
        int r=0;//记录回文字符串的边界
        for (int k=0;k<s.length();k++){//依次将每一个字符作为中心向外寻找

            //奇数回文
            int left=k;
            int right=k;
            while (left>0&&right<s.length()-1){
                if (s.charAt(left-1)==s.charAt(right+1)){//中心向两边扩散
                    left--;
                    right++;
                }
                else break;
            }
            //此时right-left为当前回文字符串长度,且为相应坐标
            if (right-left>r-l){
                r=right;
                l=left;//保存边界值,并且不断更新
            }
            left=k;
            right=k-1;
            while (left>0&&right<s.length()-1){
                if (s.charAt(left-1)==s.charAt(right+1)){
                    left--;
                    right++;
                }
                else break;
            }
            if (right-left>r-l){
                r=right;
                l=left;
            }
        }
        return s.substring(l,r+1);
    }

方法二:动态规划

对于一个子串而言,如果它是回文串,并且长度大于 2,那么将它首尾的两个字母去除之后,它仍然是个回文串。例如对于字符串 “ababa”,如果我们已经知道 “bab” 是回文串,那么 “ababa” 一定是回文串,这是因为它的首尾两个字母都是 “a”。

P(i,j) 表示字符串 s的第 i到 j个字母组成的串是否为回文串:

其他情况:s[i,j]不是回文串、i>j非法越界

动态规划的状态转移方程:P(i,j)=P(i+1,j-1) \wedge (S_{i}==S_{j}) 

只有 s[ i+1 : j−1 ]是回文串,并且 s的第 i 和 j个字母相同时,s[ i : j ] 才会是回文串。

状态转移方程中,我们是从长度较短的字符串向长度较长的字符串进行转移的,因此一定要注意动态规划的循环顺序。

动态规划中的边界条件,即子串的长度为 1或 2。对于长度为 1 的子串,它显然是个回文串;对于长度为 2 的子串,只要它的两个字母相同,就是一个回文串。

public static String longestPalindrome2(String s){
        int len=s.length();
        //只有一个字符回文串是本身
        if (len<2) return s;
        int maxlen=1;//记录最长回文串长度
        int start=0;//记录最长回文串的起始位置
        //max与start 共同确定最长回文串
        boolean [][]dp = new boolean[len][len];//dp[i][j]表示字符串s[i:j]是否为回文串
        for (int i=0;i<len;i++) dp[i][i]=true;
        //在状态转移方程中,是从长度较短的字符串向长度较长的字符串进行转移的,因此一定要注意动态规划的循环顺序。
        //故第一循环从字串长度开始
        for (int l=2;l<=len;l++){
            for (int i=0;i<len;i++){//起始位置遍历(左边界)
                int j=i+l-1;//字串的有边界
                if (j>=len) break;//有边界越界
                if (s.charAt(i)==s.charAt(j)){
                    if (j-i<3) dp[i][j]=true;//若字串长度为三,两侧确定,该字符即为回文串(i+1与j-1可能会越界)
                    else dp[i][j]=dp[i+1][j-1];
                }
                else dp[i][j]=false;
                if (dp[i][j]&&(j-i+1>maxlen)){//s[i,j]为回文串,且长度大于目前最大长度
                    maxlen=j-i+1;//更新最长回文串长度
                    start=i;//更新字串左边界
                }
            }
        }
        return s.substring(start,start+maxlen);//左取右不取,因此右边界为start+maxlen不用减1
    }

反转字符串中的单词

给你一个字符串 s ,请你反转字符串中 单词 的顺序。

单词 是由非空格字符组成的字符串。s 中使用至少一个空格将字符串中的 单词 分隔开。

返回 单词 顺序颠倒且 单词 之间用单个空格连接的结果字符串。

注意:输入字符串 s中可能会存在前导空格、尾随空格或者单词间的多个空格。返回的结果字符串中,单词间应当仅用单个空格分隔,且不包含任何额外的空格。

示例 1:

输入:s = "the sky is blue"
输出:"blue is sky the"

示例 2:

输入:s = "  hello world  "
输出:"world hello"
解释:反转后的字符串中不能存在前导空格和尾随空格。

示例 3:

输入:s = "a good   example"
输出:"example good a"
解释:如果两个单词间有多余的空格,反转后的字符串需要将单词间的空格减少到仅有一个。

知识点

        String str = "a b  c    d";
		String[] arr1 = str.split(" "); //仅分割一个空格
	    String[] arr2 = str.split("s");
	    String[] arr3 = str.split("\t");  //空格
	    // 正则表达式\s表示匹配任何空白字符,+表示匹配一次或多次
	    String[] arr4 = str.split("\\s+"); //分割一个或者多个空格

trim() 方法用于删除字符串的头尾空白符。
Collections.reverse(wordList) 对列表进行反转
String.join(添加的分隔符,添加分隔符的字符串)

方法一:

public static String reverseWords(String s) {
        String []str1=s.split("\\s+");
        String ss="";
        for (int i= str1.length-1;i>=0;i--){
            ss=ss+str1[i];
            if (i==0||str1[i-1].equals(""))break;
            ss=ss+" ";
        }
        return ss;
    }

方法二:

public String reverseWords(String s) {
        // 除去开头和末尾的空白字符
        s = s.trim();
        // 正则匹配连续的空白字符作为分隔符分割
        List<String> wordList = Arrays.asList(s.split("\\s+"));
        Collections.reverse(wordList);
        return String.join(" ", wordList);
    }

KMP算法

1、next数组的确定

求字符串的最长公共前缀后缀(不包括本身)的长度

字符串 abcba的前缀:a,ab,abc,abcb

                         后缀:bcba,cba,ba,a

最长公共前缀(a)长度为1

public static int MAXlen(String str){
        List<String> list1 = new ArrayList<>();
        List<String> list2 = new ArrayList<>();
        for (int i=1;i< str.length();i++){
            list1.add(str.substring(0,i));
            list2.add(str.substring(i,str.length()));
        }
        int maxlen=0;
        for (int i=0;i<list1.size();i++){
            if (list1.get(i).equals(list2.get(list1.size()-i-1))) {
                if (list1.get(i).length()>maxlen) maxlen=list1.get(i).length();
            }
        }
        return maxlen;
    }

最大长度值数组Len[i] 表示以str[i]结束的字符的最长公共前缀后缀的长度

对于字符串abcba

Len[0]:a的前缀后缀均为空,故Len[0]=0;

Len[1]:ab的前缀为a,后缀为b,故Len[1]=0+len0

Len[2]:abc的前缀为a,ab,后缀为bc,c,故Len[2]=0+len1

Len[3]:abcb的前缀为a,ab,abc,后缀为bcb,bc,b,故Len[3]=0+len2

Len[4]:abcba的前缀为a,ab,abc,abcb,后缀为bcba,cba,ba,a故Len[4]=1+len3

next 数组相当于“最大长度值” 整体向右移动一位,然后初始值赋为-1

对于abcba的next数组为

next[0]:-1

next[1]:0

next[2]:0

next[3]:0

next[4]:0

next数组的求法:

public static void Snext(int []next,String str){
        int len=str.length();
        int k=-1;//对next进行赋值
        int j=0;
        next[j]=k;//next[0]=-1;
        while (j<len-1){//前缀最多到倒数第二个字符
            //str.charAt(j)表示前缀
            //str.charAt(k)表示后缀
            if (k==-1||str.charAt(j)==str.charAt(k)){//前缀与后缀遇到一样的加1继续比较
                j++;
                k++;
                next[j]=k;
            }else k=next[k];//遇到不一样的k返回上次一样的值重新比较
        }
    }

KMP算法

给你两个字符串 str1 和 str2 ,请你在 str1 字符串中找出 str2 字符串的第一个匹配项的下标(下标从 0 开始)。如果 str2 不是 str1 的一部分,则返回  -1 。

示例 1:

输入:str1 = "sadbutsad", str2 = "sad"
输出:0
解释:"sad" 在下标 0 和 6 处匹配。
第一个匹配项的下标是 0 ,所以返回 0 。
示例 2:

输入:str1 = "leetcode", str2 = "leeto"
输出:-1
解释:"leeto" 没有在 "leetcode" 中出现,所以返回 -1 。

传统思想:先从str1的第一个字符开始与str2第一个字符进行比较,若相同将str1的第二个字符与str2的第二个字符进行比较,直至str2比较完成。若中途遇到不相等的字符,则str1从第二个字符开始与str2的第一个字符比较。

public static int KMP2(String haystack, String needle) {
        int len1=haystack.length();
        int len2=needle.length();
        int i=0;
        int j=0;
        int k=0;
        while (i<len1&&j<len2){
            if (haystack.charAt(i)==needle.charAt(j)){
                i++;
                j++;
            }else {
                j = 0;
                k++;
                i=k;
            }
        }
        if (j==len2) return i-j;
        else return -1;
    }

思想:先从str1的第一个字符开始与str2第一个字符进行比较,若相同将str1的第二个字符与str2的第二个字符进行比较,直至str2比较完成。若中途遇到不相等的字符,则str1遍历位置不变与str2的第next[j] (j为不等时str2的字符下标)个字符比较。

public int KMP(String haystack, String needle) {
        int len1=haystack.length();
        int len2=needle.length();
        int i=0;
        int j=0;
        while (i<len1&&j<len2){
            if (j==-1||haystack.charAt(i)==needle.charAt(j)){
                i++;
                j++;
            }else j=next[j];
        }
        if (j==len2) return i-j;
        else return -1;
    }

 最长连续递增子序列

(1,9,2,5,7,3,4,6,8,0)中最长的递增子序列为(3,4,6,8)。

    private static List<Integer> MaxlenNum(int[] nums) {
        List<Integer> list  = new ArrayList<Integer>();//最长递增序列
        for(int i=0; i<nums.length; i++) {//递增序列的头
            List<Integer> l=new ArrayList<Integer>();//临时递增序列
            l.add(nums[i]);
            int j = i+1;//递增序列的尾
            while (j < nums.length){
                if (nums[j-1]<nums[j]) {
                    l.add(nums[j]);
                    j++;
                }else break;
            }
            if (list.size()<l.size()) list=l;
        }
        return list;
    }

出现次数大于一半的数

思路:

1、利用hash表存储每一个数对应出现的次数即可

2、利用两两消除法。定一个基准值,初始化出现次数为1。依次与其他元素比较,若相等则该元素的出现次数+1,否则出现次数-1.并且一旦基准值的出现次数为0,则舍弃该基准值,基准值进行更新。最终留下来的便是出现次数大于一半的元素。

对于方法二的情况分析:

    private static int search_ban1(int[] nums) {
        HashMap<Integer,Integer> map = new HashMap<Integer,Integer>();
        for (int i=0;i<nums.length;i++){
            map.put(nums[i],0);
        }
        for (int i=0;i<nums.length;i++){
            map.replace(nums[i],map.get(nums[i])+1);
        }
        System.out.println(map);
        for (int i=0;i<map.size();i++){
            if (map.get(nums[i])>= nums.length/2) return nums[i];
        }
        return -1;
    }
    private static int search_ban(int[] nums) {
        int can=nums[0];
        int ntimes=1;
        for(int i=1; i<nums.length; i++) {
            if(ntimes==0){
                can=nums[i];
                ntimes=1;
                continue;
            }
            if(nums[i]==can) ntimes++;
            else ntimes--;
        }
        return can;
    }

数组元素能组合成的最小数

数组为{3,32,321}三个能拼接组成的最小数为321323。

主要思想:思路采用两两组合比较的方法,先比较332与323,确定323之后,再用323与321组合比较即可。

public class 数组能排成的最小的数 {
	public static void main(String[] args) {
		Integer []nums = new Integer[] {3,32,321,1235};
		Arrays.sort(nums,new Comparator<Integer>(){
			//自定义sort的比较规则,默认按照大小排序
			@Override
			public int compare(Integer o1, Integer o2) {
				// TODO Auto-generated method stub
				//将数字变为字符进行两种形式的拼接
				String s1=o1+""+o2;
				String s2=o2+""+o2;
				
				return s1.compareTo(s2);
			}
		});
		//此时nums已按照以上规则排好序
		StringBuilder sb = new StringBuilder();
		for (int i = 0; i < nums.length; i++) {
			sb.append(nums[i]);
		}
		System.out.println(sb.toString());
	}
}

自定义sort比较方法的用法

sort(数组名, 起始下标, 终止下标, new cmp());

基本方法

int compare(Object o1, Object o2) 返回一个基本类型的整型
如果要按照升序排序,
则o1 小于o2,返回-1(负数),相等返回0,01大于02返回1(正数)
如果要按照降序排序
 则o1 小于o2,返回1(正数),相等返回0,01大于02返回-1(负数)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Yoin.

感谢各位打赏!!

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

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

打赏作者

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

抵扣说明:

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

余额充值