奇妙的算法【11】LeetCode-專屬算法面試題彙總

  這個是LeetCode上面的編程訓練專項頁面,地址:https://leetcode-cn.com/explore/interview/card/top-interview-quesitons-in-2018/262/summery/java

  整體,比較系統、全面。在解決這些問題的時候,我都是先嚐試使用本身的方法coding一遍,以後在看看其餘比較巧妙的解決方法學習一下。面試

須要特殊技巧解決的難題:①切割回文;②正則表達式

0,熱身編程題

  0.1只出現一次的數字【算法簡單、可是想要達到優雅就須要動一動腦子

  給定一個非空整數數組,除了某個元素只出現一次之外,其他每一個元素均出現兩次。找出那個只出現了一次的元素。算法

package com.cnblogs.mufasa.QA1_makeAhotDOU;

import org.junit.Test;
import java.util.Arrays;

public class Solution1 {

    //速度也還行,時間複雜度大體爲O(nlogn+n/2),相對性能要差一些
    public static int singleNumber1(int[] nums) {
        Arrays.sort(nums);
        int len=nums.length-1;
        for(int i=0;i<len;i+=2){
            if(nums[i]!=nums[i+1]){
                return nums[i];
            }
        }
        return nums[len];
    }

    //***最優化的解法***,時間複雜度爲O(n)
    public static int singleNumber(int[] nums) {
        int temp=nums[0];
        for(int i=1;i<nums.length;i++){
            temp^=nums[i];
        }
        return temp;
    }

    @Test
    public void test() {
//        int[] nums=new int[]{1,1,2,2,6,7,7,6};
        int[] nums=new int[]{4,1,2,1,2};
        System.out.println(singleNumber(nums));
        System.out.println(singleNumber1(nums));
    }
}
View Code

 

  0.2求衆數【簡單,奇技淫巧

  給定一個大小爲 的數組,找到其中的衆數。衆數是指在數組中出現次數大於 ⌊ n/2 ⌋ 的元素。編程

  你能夠假設數組是非空的,而且給定的數組老是存在衆數。數組

package com.cnblogs.mufasa.QA1_makeAhotDOU;

import org.junit.Test;

import java.util.Arrays;
import java.util.Map;
import java.util.TreeMap;

public class Solution2 {

    //最完美的解決方法,時間複雜度能夠下降到O(NlogN)
    public int majorityElement1(int[] nums) {
        Arrays.sort(nums);
        return nums[nums.length/2];
    }

    //利用TreeMap來進行排序,更新出現頻率大小
    public int majorityElement(int[] nums) {
        TreeMap<Integer,Integer> treeMap=new TreeMap<>();
        for(int i=0;i<nums.length;i++){
            if(treeMap.get(nums[i])!=null){
                treeMap.put(nums[i],treeMap.get(nums[i])+1);
            }else {
                treeMap.put(nums[i],1);
            }
        }
        int loc=-1,max=0;
        for(Map.Entry<Integer,Integer> kv:treeMap.entrySet()){
            if(kv.getValue()>max){
                loc=kv.getKey();
                max=kv.getValue();
                if(max>nums.length/2){
                    break;
                }
            }
        }
        return loc;
    }

    @Test
    public void test() {
        int[] nums=new int[]{2,2,1,1,1,2,2};
        System.out.println(majorityElement1(nums));
        System.out.println(majorityElement(nums));
    }
}
View Code

 

  0.3搜索二維矩陣 II【題目中給出的條件通常狀況下都會本身的意義,例如:有序

編寫一個高效的算法來搜索 m x n 矩陣 matrix 中的一個目標值 target。該矩陣具備如下特性:性能優化

  • 每行的元素從左到右升序排列。
  • 每列的元素從上到下升序排列。
package com.cnblogs.mufasa.QA1_makeAhotDOU;

import org.junit.Test;

public class Solution3 {

    //1,暴力破解法【遍歷全部位置】因爲題目已經給出這個matrix是有序的,因此暴力法明顯就浪費了這種已知條件
    public boolean searchMatrix1(int[][] matrix, int target) {
        if(matrix==null||matrix.length==0||matrix[0].length==0){
            return false;
        }
        int xLen=matrix.length,yLen=matrix[0].length;
        for(int i=0;i<xLen;i++){
            for(int j=0;j<yLen;j++){
                if(matrix[i][j]==target){
                    return true;
                }
            }
        }
        return false;
    }

    //2,二分查找法,利用上有序這一條件
    public boolean searchMatrix(int[][] matrix, int target) {
        if(matrix==null||matrix.length==0||matrix[0].length==0){
            return false;
        }
        int x=matrix.length-1;
        int y=0,yLen=matrix[0].length;
        while (y<yLen&&x>=0){
            if(matrix[x][y]>target){
                x--;
            }else if(matrix[x][y]<target){
                y++;
            }else {
                return true;
            }
        }
        return false;
    }

    @Test
    public void test(){
        int[][] matrix={
                {1,   4,  7, 11, 15},
                {2,   5,  8, 12, 19},
                {3,   6,  9, 16, 22},
                {10, 13, 14, 17, 24},
                {18, 21, 23, 26, 30}};
//        System.out.println(searchMatrix(matrix,5));
//        System.out.println(searchMatrix(matrix,20));
        System.out.println(searchMatrix(matrix,22));
    }
}
View Code

 

  0.4合併兩個有序數組【好幾種方法求解,可是要知足限制條件就有點技巧了】

  給定兩個有序整數數組 nums1 和 nums2,將 nums2 合併到 nums1 使得 num1 成爲一個有序數組。app

  說明:dom

  • 初始化 nums1 和 nums2 的元素數量分別爲 m 和 n
  • 你能夠假設 nums1 有足夠的空間(空間大小大於或等於 m + n)來保存 nums2 中的元素。
package com.cnblogs.mufasa.QA1_makeAhotDOU;

import org.junit.Test;
import java.util.Arrays;

public class Solution4 {

    //1,普通的合併排序,算法就是O((n+m)log(n+m)),其中
    public void merge1(int[] nums1, int m, int[] nums2, int n) {
        System.arraycopy(nums2, 0, nums1, m, n);
        Arrays.sort(nums1);
        SysPrint(nums1);
    }

    //2,新開闢地址空間逐項進行大小比較轉移
    public void merge2(int[] nums1, int m, int[] nums2, int n) {
        int[] copy_nums1=new int[m];
        System.arraycopy(nums1,0,copy_nums1,0,m);//nums1的大小是知足咱們要求的,可是其中的內容不必定夠
        int p1=0,p2=0;
        int p=0;

        while (p1<m&&p2<n){
            nums1[p++]=copy_nums1[p1]<nums2[p2]?copy_nums1[p1++]:nums2[p2++];
        }
        if(p1<m){
            System.arraycopy(copy_nums1,p1,nums1,p1+p2,m+n-p1-p2);
        }else {
            System.arraycopy(nums2,p2,nums1,p1+p2,m+n-p1-p2);
        }
        SysPrint(nums1);
    }

    private static void SysPrint(int[] nums1){
        System.out.print("["+nums1[0]);
        for(int i=1;i<nums1.length;i++){
            System.out.print(","+nums1[i]);
        }
        System.out.println("]");
    }

    @Test
    public void test(){
        int[] nums1={1,2,3,0,0,0};
//        int[] nums2={4,5,6};
        int[] nums2={2,5,6};
        int m=3,n=3;

//        int[] nums1={0};
//        int[] nums2={1};
//        int m=0,n=1;

        merge1( nums1, m, nums2, n);
        merge2( nums1, m, nums2, n);
    }
}
View Code

 

  0.5雞蛋掉落【看起來很簡單,可是其實很難的

  你將得到 K 個雞蛋,並可使用一棟從 1 到 N  共有 N 層樓的建築。ide

  每一個蛋的功能都是同樣的,若是一個蛋碎了,你就不能再把它掉下去。

  你知道存在樓層 F ,知足 0 <= F <= N 任何從高於 F 的樓層落下的雞蛋都會碎,從 F 樓層或比它低的樓層落下的雞蛋都不會破。

  每次移動,你能夠取一個雞蛋(若是你有完整的雞蛋)並把它從任一樓層 X 扔下(知足 1 <= X <= N)。

  你的目標是確切地知道 F 的值是多少。

  不管 F 的初始值如何,你肯定 F 的值的最小移動次數是多少?

package com.cnblogs.mufasa.QA1_makeAhotDOU;

import org.junit.Test;

import java.util.HashMap;
import java.util.Map;

public class Solution5 {

    //1,動態規劃加二分搜索
    public int superEggDrop1(int K, int N) {
        return dp(K, N);
    }

    Map<Integer, Integer> memo = new HashMap();
    public int dp(int K, int N) {
        if (!memo.containsKey(N * 100 + K)) {
            int ans;
            if (N == 0)
                ans = 0;
            else if (K == 1)
                ans = N;
            else {
                int lo = 1, hi = N;
                while (lo + 1 < hi) {
                    int x = (lo + hi) / 2;
                    int t1 = dp(K - 1, x - 1);
                    int t2 = dp(K, N - x);

                    if (t1 < t2)
                        lo = x;
                    else if (t1 > t2)
                        hi = x;
                    else
                        lo = hi = x;
                }
                ans = 1 + Math.min(Math.max(dp(K - 1, lo - 1), dp(K, N - lo)),
                        Math.max(dp(K - 1, hi - 1), dp(K, N - hi)));
            }
            memo.put(N * 100 + K, ans);
        }
        return memo.get(N * 100 + K);
    }

    //2,自底向上的dp算法
    public int superEggDrop2(int K, int N) {
        //初始化dp的最原始記錄
        int[] dp = new int[N+1];
        for (int i = 0; i <= N; ++i)
            dp[i] = i;

        //逐步更新數據
        for (int k = 2; k <= K; ++k) {
            int[] dp2 = new int[N+1];
            int x = 1;
            for (int n = 1; n <= N; ++n) {
                while (x < n && Math.max(dp[x-1], dp2[n-x]) > Math.max(dp[x], dp2[n-x-1]))
                    x++;

                dp2[n] = 1 + Math.max(dp[x-1], dp2[n-x]);
            }

            dp = dp2;
        }

        return dp[N];
    }


    //3,遞歸調用法【參考信息論的知識】--失敗的方法,後續繼續剛啊
    public int superEggDrop(int K, int N) {
        return recurEggDrop(K,N+1,true);//以個數爲準,true表示初始化判決
    }

    private int recurEggDrop(int K,int N,boolean flag){
        if(flag&&(K==1||N<=3)){
            return N-1;
        }else if(!flag&&(K==1||N<=3)){
            return N;
        }
        int pre=(N-1)/2;
        return Math.max(1+recurEggDrop( K-1, pre,true),1+recurEggDrop( K, pre+(N+1)%2,false));
    }
    
    @Test
    public void test(){
//        int K = 3, N = 14;
//        int K = 2, N = 2;
//        int K = 2, N = 3;
        int K = 2, N = 6;
        System.out.println(superEggDrop1(K,N));
    }
}
View Code

 

1,字符串

  1.1驗證迴文【之前寫過,比較簡單】

  給定一個字符串,驗證它是不是迴文串,只考慮字母和數字字符,能夠忽略字母的大小寫。

package com.cnblogs.mufasa.QA1_String;

import org.junit.Test;


public class Solution1 {

    //LeetCode上耗時最短的算法
    public boolean isPalindrome(String s) {
        if(s==null){
            return false;
        }else if(s.length()<=1){
            return true;
        }
        int i = 0;
        int j = s.length() - 1;
        char[] cs = s.toCharArray();

        while(i < j){
            if(!((cs[i] >= '0' && cs[i] <= '9')
                    || (cs[i] >= 'A' && cs[i] <= 'Z')
                    || (cs[i] >= 'a' && cs[i] <= 'z'))){//判斷不是元字符,直接移動光標便可,跳出本次循環
                i++;
                continue;
            }
            if(!((cs[j] >= '0' && cs[j] <= '9')
                    || (cs[j] >= 'A' && cs[j] <= 'Z')
                    || (cs[j] >= 'a' && cs[j] <= 'z'))){
                j--;
                continue;
            }
            if(cs[i] == cs[j]){//char相同,直接先後光標移動,
                i++;
                j--;
                continue;
            }

            if((cs[i] - cs[j] == 32 || cs[i]-cs[j] == -32)
                    && cs[i] > '9' && cs[j] > '9'){//ignoreCase的手搖式方法
                i++;
                j--;
                continue;
            }
            return false;
        }
        return true;
    }

    //使用到多於的函數來輔助進行判斷,①正則表達式;②toLowerCase
    public boolean isPalindrome1(String s) {
        s=s.replaceAll("\\W","");
        s=s.toLowerCase();
        int len=s.length();
        for(int i=0;i<len/2;i++){
            if(s.charAt(i)!=s.charAt(len-1-i)){
                return false;
            }
        }
        return true;
    }

    @Test
    public void test(){
        String str0="A man, a plan, a canal: Panama";
        String str1="race a car";
        System.out.println(isPalindrome(str0));
        System.out.println(isPalindrome(str1));
    }
}
View Code

 

  1.2分割回文串【難度等級2,新知識:回溯剪枝!!!】有些麻煩的,雖然遍歷能夠作出來可是時間複雜度過高

  給定一個字符串 s,將 s 分割成一些子串,使每一個子串都是迴文串。

  返回 s 全部可能的分割方案。

package com.cnblogs.mufasa.QA1_String;

import org.junit.Test;

import java.util.ArrayList;
import java.util.List;

public class Solution2 {

    //1,採用分治法求解的一種思路
    public List<List<String>> partition(String s) {
        return partitionHelper(s, 0);
    }

    //遞歸&分治:大問題進行拆分化解爲相同原理的小問題,以後將結果合併
    private List<List<String>> partitionHelper(String s, int start) {
        if (start == s.length()) {//內部是一個null值,退出的出口
            List<String> list = new ArrayList<>();
            List<List<String>> ans = new ArrayList<>();
            ans.add(list);
            return ans;
        }

        List<List<String>> ans = new ArrayList<>();
        for (int i = start; i < s.length(); i++) {
            if (isPalindrome(s.substring(start, i + 1))) {//當前切割後是迴文串才考慮
                String left = s.substring(start, i + 1);
                //遍歷後邊字符串的全部結果,將當前的字符串加到頭部
                for (List<String> l : partitionHelper(s, i + 1)) {//Recursive Node 很巧妙的一個步驟
                    l.add(0, left);
                    ans.add(l);
                }
            }
        }
        return ans;
    }

    //判斷當前字符串是否爲迴文
    private boolean isPalindrome(String s) {
        int i = 0;
        int j = s.length() - 1;
        while (i < j) {
            if (s.charAt(i) != s.charAt(j)) {
                return false;
            }
            i++;
            j--;
        }
        return true;
    }

    //2,優化分治算法,在判斷是否爲迴文的這一步驟中,咱們重複進行了不少次冗餘判斷,這個咱們能夠避免掉的
    public List<List<String>> partition1(String s) {
        int length = s.length();
        boolean[][] dp = new boolean[length][length];

        for (int len = 1; len <= length; len++) {
            for (int i = 0; i <= s.length() - len; i++) {
                int j = i + len - 1;
                //要保證dp[i + 1][j - 1] 中 i + 1 <= j - 1
                dp[i][j] = s.charAt(i) == s.charAt(j) && (len < 3 || dp[i + 1][j - 1]);//利用歷史信息優化計算
            }
        }
        return partitionHelper(s, 0, dp);
    }

    private List<List<String>> partitionHelper(String s, int start, boolean[][] dp) {
        if (start == s.length()) {
            List<String> list = new ArrayList<>();
            List<List<String>> ans = new ArrayList<>();
            ans.add(list);
            return ans;
        }
        List<List<String>> ans = new ArrayList<>();
        for (int i = start; i < s.length(); i++) {
            if (dp[start][i]) {//直接省略掉了重複判斷迴文的步驟
                String left = s.substring(start, i + 1);
                for (List<String> l : partitionHelper(s, i + 1, dp)) {
                    l.add(0, left);
                    ans.add(l);
                }
            }

        }
        return ans;
    }

    //3,回溯法
    public List<List<String>> partition2(String s) {
        boolean[][] dp = new boolean[s.length()][s.length()];
        int length = s.length();
        for (int len = 1; len <= length; len++) {
            for (int i = 0; i <= s.length() - len; i++) {
                dp[i][i + len - 1] = s.charAt(i) == s.charAt(i + len - 1) && (len < 3 || dp[i + 1][i + len - 2]);
            }
        }
        List<List<String>> ans = new ArrayList<>();
        partitionHelper(s, 0, dp, new ArrayList<>(), ans);
        return ans;
    }

    private void partitionHelper(String s, int start, boolean[][] dp, List<String> temp, List<List<String>> res) {
        //到了空串就加到最終的結果中
        if (start == s.length()) {
            res.add(new ArrayList<>(temp));
        }
        //在不一樣位置切割
        for (int i = start; i < s.length(); i++) {
            //若是是迴文串就加到結果中
            if (dp[start][i]) {
                String left = s.substring(start, i + 1);
                temp.add(left);
                partitionHelper(s, i + 1, dp, temp, res);
                temp.remove(temp.size() - 1);
            }

        }
    }

    public static void printOut(List<List<String>> arrs){
        System.out.println("[");
        for(List<String> list:arrs){
            System.out.print("\t["+list.get(0));
            for(String str:list.subList(1,list.size())){
                System.out.print(","+str);
            }
            System.out.println("]");
        }
        System.out.println("]");
    }

    @Test
    public void test(){
        String s="aabb";
//        List<List<String>> arrs=partition(s);
//        List<List<String>> arrs=partition1(s);
        List<List<String>> arrs=partition2(s);
        printOut(arrs);
    }
}
View Code

 參考連接:https://leetcode-cn.com/problems/palindrome-partitioning/solution/xiang-xi-tong-su-de-si-lu-fen-xi-duo-jie-fa-by-3-7/

 

2,數組

  2.1乘積最大子序列

  給定一個整數數組 nums ,找出一個序列中乘積最大的連續子序列(該序列至少包含一個數)。

 

  2.1.1【觸類旁通】求和最大子序列

  給定一個整數數組 nums ,找到一個具備最大和的連續子數組(子數組最少包含一個元素),返回其最大和。

package com.cnblogs.mufasa.QA2_nums;

import org.junit.Test;

public class Solution1_1 {

    //動態規劃的一類題型,將歷史數據存入sum中與當前數據進行比較
    public int maxSubArray(int[] nums) {
        int result = nums[0];   // 保存最大的結果
        int sum = 0;            // 保存當前的子序和
        for (int num : nums) {
            if (sum > 0) {     // sum是正數,意味着後面有機會再創新高,能夠繼續加
                sum += num;
            } else {           // sum是負的,還不如直接從當前位從新開始算,也比(負數+當前值)要大吧
                sum = num;
            }
            result = Math.max(result, sum);   // 每一步都更新最大值
        }
        return result;
    }

    @Test
    public void test(){
        int[] nums={-2,1,-3,4,-1,2,1,-5,4};
        System.out.println(maxSubArray(nums));
    }
}
View Code

 

 

3,堆、棧與隊列

4,鏈表

5,哈希與映射

6,樹

7,排序與檢索

  7.1最大數【這個題目,我以前作過】

  給定一組非負整數,從新排列它們的順序使之組成一個最大的整數。

package com.cnblogs.mufasa.QA7_sort_search;

import org.junit.Test;
import java.util.Arrays;

public class Solution1 {

    //1,直接利用CompareTo進行排序
    public String largestNumber1(int[] nums) {
        String[] arr=new String[nums.length];
        for(int i=0;i<nums.length;i++){
            arr[i]=""+nums[i];
        }
        Arrays.sort(arr,(a,b)->{
            String ab=a+b;
            String ba=b+a;
            int len=ab.length();
            for(int i=0;i<len;i++){
                int temp=ab.charAt(i)-ba.charAt(i);
                if(temp<0){
                    return 1;
                }else if(temp>0){
                    return -1;
                }
            }
            return 0;
        });

        StringBuilder sb=new StringBuilder();
        boolean flag=true;
        for(String temp:arr){
            if(flag){
                if(!temp.equals("0")){
                    sb.append(temp);
                    flag=false;
                }
            }else {
                sb.append(temp);
            }
        }
        if(arr.length!=0&&sb.length()==0){
            sb.append(""+0);
        }
        return sb.toString();
    }

    //2,對以上代碼進行優化
    public String largestNumber2(int[] nums){
        String[] arrs = new String[nums.length];
        for(int i =0; i < nums.length; i++){
            arrs[i] = String.valueOf(nums[i]);
        }
        Arrays.sort(arrs, (a,b)->{
            String ab=a+b;
            String ba=b+a;
            return -ab.compareTo(ba);
        });
        StringBuilder sb = new StringBuilder();
        for(String i : arrs){
            sb.append(i);
        }
        String str=sb.toString();
        if(str.startsWith("0")){
            return "0";
        }
        return str;
    }


    @Test
    public void test(){
//        int[] nums={10,2};
        int[] nums={3,30,9,34,5};
//        int[] nums={0,0,0};
        System.out.println(largestNumber2(nums));
    }
}
View Code

  

  7.2尋找峯值【看到複雜度要求,就應該直接上手二分查找法】

  峯值元素是指其值大於左右相鄰值的元素。

  給定一個輸入數組 nums,其中 nums[i] ≠ nums[i+1],找到峯值元素並返回其索引。

  數組可能包含多個峯值,在這種狀況下,返回任何一個峯值所在位置便可。

  你能夠假設 nums[-1] = nums[n] = -∞

package com.cnblogs.mufasa.QA7_sort_search;

import org.junit.Test;

public class Solution3 {

    //1,遍歷法:時間複雜度爲O(n),很顯然不符合人家的要求
    public int findPeakElement1(int[] nums) {
        int len=nums.length;
        int[] flag={0,0};
        int loc=0;
        for(int i=0;i<len-1;i++){
            flag[0]=flag[1];
            if(nums[i]<nums[i+1]){
                flag[1]=-1;
                loc=i+1;
            }else if(nums[i]>nums[i+1]){
                flag[1]=1;
            }

            flag[1]=(nums[i]==nums[i+1]?0:(nums[i]<nums[i+1]?-1:1));
            if(flag[0]==-1&&flag[1]==1){
                return i;
            }
        }
        return loc;
    }

    //2,二分查找法求解
    public int findPeakElement(int[] nums) {
        int left = 0, right = nums.length - 1;
        for (; left < right; ) {
            int mid = left + (right - left) / 2;
            if (nums[mid] > nums[mid + 1]) {
                right = mid;
            } else {
                left = mid + 1;
            }
        }
        return left;
    }

    @Test
    public void test(){
//        int[] nums={1,2,1,3,5,6,4};
//        int[] nums={1,2,3,1};
        int[] nums={2,1};
//        int[] nums={1,2};
        System.out.println(findPeakElement(nums));
    }
}
View Code

 

  7.3擺動排序 II【毫無頭緒】

  給定一個無序的數組  nums,將它從新排列成  nums[0] < nums[1] > nums[2] < nums[3]... 的順序。
 

  觸類旁通-擺動排序

  給你一個無序的數組 nums, 將該數字 原地 重排後使得 nums[0] <= nums[1] >= nums[2] <= nums[3]...

 

 

  7.4尋找重複數【弗洛伊德的烏龜和兔子-循環檢測】

  給定一個包含 n + 1 個整數的數組 nums,其數字都在 1 到 之間(包括 1 和 n),可知至少存在一個重複的整數。假設只有一個重複的整數,找出這個重複的數。

package com.cnblogs.mufasa.QA7_sort_search;

import org.junit.Test;

import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;

public class Solution4 {

    //1,遍歷法求解,時間複雜度爲O(n^2) 好像恰好知足複雜度要求
    public int findDuplicate1(int[] nums) {
        int len=nums.length;
        for(int i=0;i<len-1;i++){
            for(int j=i+1;j<len;j++){
                if((nums[i]^nums[j])==0){
                    return nums[i];
                }
            }
        }
        return -1;
    }

    //2,排序法,不符合題目要求的只讀限制條件
    public int findDuplicate2(int[] nums) {
        Arrays.sort(nums);
        for (int i = 1; i < nums.length; i++) {
            if (nums[i] == nums[i-1]) {
                return nums[i];
            }
        }

        return -1;
    }

    //3,開闢新空間處理,空間複雜度爲O(n),時間複雜度爲O(n),不知足空間複雜度O(1)的限制
    public int findDuplicate3(int[] nums) {
        Set<Integer> seen = new HashSet<Integer>();
        for (int num : nums) {
            if (seen.contains(num)) {
                return num;
            }
            seen.add(num);
        }
        return -1;
    }
    
    //4,弗洛伊德的烏龜和兔子(循環檢測)
    public int findDuplicate4(int[] nums) {
        int tortoise = nums[0];
        int hare = nums[0];
        do{
            tortoise = nums[tortoise];
            hare = nums[nums[hare]];
        }while(tortoise!=hare);

        int p1=nums[0];
        int p2=tortoise;
        while(p1!=p2){
            p1 = nums[p1];
            p2 = nums[p2];
        }
        return p1;
    }

    @Test
    public void test(){
//        int[] nums={3,1,3,4,2};
        int[] nums={2,5,9,6,9,3,8,9,7,1};
        System.out.println(findDuplicate4(nums));
    }
}
View Code
 

  7.5計算右側小於當前元素的個數【二叉樹方法,沒有解決!】

  給定一個整數數組 nums,按要求返回一個新數組 counts。數組 counts 有該性質: counts[i] 的值是  nums[i] 右側小於 nums[i] 的元素的數量。

package com.cnblogs.mufasa.QA7_sort_search;

import org.junit.Test;
import org.w3c.dom.Node;

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;

public class Solution5 {

    //1,暴力法,能夠求解,太low了,不想寫

    //2,利用歷史數據進行更新迭代,比暴力法要好一些,可是複雜度仍是O(n^2)
    public List<Integer> countSmaller2(int[] nums) {
        List<Integer> list=new ArrayList<>();
        List<Integer> queue=new ArrayList<>(nums.length);

        if(nums==null||nums.length==0){
            return list;
        }
        int len=nums.length;
        list.add(0);
        queue.add(nums[len-1]);

        boolean flag=true;
        for(int i=len-2;i>=0;i--){
            int cnt=len-i-1;
            int pre=nums[i];
            int lenQ=queue.size();

            for(int j=0;j<lenQ;j++){
                if(pre<=queue.get(j)){//前面的數值有大於等於本數字的
                    cnt--;
                }else {
                    flag=false;
                    queue.add(j,pre);
                    break;
                }
            }
            if(flag){
                list.add(0,0);
                queue.add(pre);
            }else {
                list.add(0,cnt);
                flag=true;
            }
        }
        return list;
    }

    //3,在上面的基礎上添加二分查找法來進行性能優化,複雜度爲O(nlogn)
    public List<Integer> countSmaller3(int[] nums) {
        List<Integer> list=new ArrayList<>();
        List<Integer> queue=new ArrayList<>(nums.length);

        if(nums==null||nums.length==0){
            return list;
        }
        int len=nums.length;
        list.add(0);
        queue.add(nums[len-1]);

        boolean flag=true;
        for(int i=len-2;i>=0;i--){
            int cnt=len-i-1;
            int pre=nums[i];
            int lenQ=queue.size();

            //這個部分更換爲二分查找法
            int index=binarySearch(queue,pre);
            queue.add(index,pre);
            list.add(0,lenQ-index);
        }
        return list;
    }

    private static int binarySearch(List<Integer> arr,int target){//並非查找固定值,而是查找特定位置
        if(arr.size()==1){
            return (arr.get(0)<target?0:1);
        }
        int x=0,y=arr.size()-1,mid=1;
        while (x<y){
            mid=(x+y)/2;
            int temp=arr.get(mid);
            if(temp<target){
                y=mid;
            }else if(temp>target){
                x=mid;
            }else {
                return mid;
            }
        }
        return y;
    }

    //4,經過二叉樹結構來完成
    static int smallSum;
    private class TreeNode {
        int val;
        int count;
        TreeNode left;
        TreeNode right;
        TreeNode(int val) {
            this.val = val;
            this.count = 0;
            this.left = null;
            this.right = null;
        }
    }

    public List<Integer> countSmaller4(int[] nums) {
        int len = nums.length;
        List<Integer> result = new LinkedList();
        if (len < 1) return result;
        TreeNode root = new TreeNode(nums[len - 1]);
        for (int i = len - 2; i >= 0; i--) {
            smallSum = 0;
            insert(root, new TreeNode(nums[i]));
            result.add(0, smallSum);
        }
        result.add(0);
        return result;
    }

    private void insert(TreeNode curr, TreeNode newNode) {//利用搜索二叉樹的結構
        if (curr == null) return;
        if (newNode.val > curr.val) {
            smallSum += curr.count + 1;
            if (curr.right == null) curr.right = newNode;
            else insert(curr.right, newNode);
        }else {
            curr.count++;
            if (curr.left == null) curr.left = newNode;
            else insert(curr.left, newNode);
        }
    }

    @Test
    public void test(){
//        int[] nums={10,5,4,3,2,1};
//        List<Integer> arr=new ArrayList<>();
//        for(int i:nums){
//            arr.add(i);
//        }

//        int[] nums={5,2,6,1};
//        int[] nums={};
        int[] nums={26,78,27,100,33,67,90,23,66,5,38,7,35,23,52,22,83,51,98,69,81,32,78,28,94,13,2,97,3,76,99,51,9,21,84,66,65,36,100,41};
        System.out.println(countSmaller3(nums));
//        System.out.println(binarySearch(arr,5));
    }
}
/*
[10,27,10,35,12,22,28,8,19,2,12,2,9,6,12,5,17,9,19,12,14,6,12,5,12,3,0,10,0,7,8,4,0,0,4,3,2,0,1,0]
[5,27,5,35,7,22,28,3,19,-8,11,-8,4,1,12,0,17,9,19,12,14,1,12,0,12,-3,0,10,0,7,8,4,0,0,4,3,2,0,1,0]
 */
View Code

 

8,動態規劃

9,圖論

10,數學&位運算

  須要進一步本身手撕一遍的問題:10.二、10.三、10.四、

  10.1只出現一次的數字【見熱身編程,異或解決問題,簡單問題】

  10.2直線上最多的點數

  給定一個二維平面,平面上有 個點,求最多有多少個點在同一條直線上。

package com.cnblogs.mufasa.QA10_math;

import org.junit.Test;

import java.util.HashMap;
import java.util.Map;

public class Solution2 {
    public int maxPoints(int[][] points) {
        int n = points.length;
        if (n == 0) return 0;
        if (n == 1) return 1;
        int res = 0;
        for (int i = 0; i < n - 1; i++) {
            Map<String, Integer> slope = new HashMap<>();
            int repeat = 0;
            int tmp_max = 0;
            for (int j = i + 1; j < n; j++) {
                int dy = points[i][1] - points[j][1];
                int dx = points[i][0] - points[j][0];
                if (dy == 0 && dx == 0) {
                    repeat++;
                    continue;
                }
                int g = gcd(dy, dx);
                if (g != 0) {
                    dy /= g;
                    dx /= g;
                }
                String tmp = String.valueOf(dy) + "/" + String.valueOf(dx);
                slope.put(tmp, slope.getOrDefault(tmp, 0) + 1);
                tmp_max = Math.max(tmp_max, slope.get(tmp));
            }
            res = Math.max(res, repeat + tmp_max + 1);
        }
        return res;
    }

    private int gcd(int dy, int dx) {
        if (dx == 0) return dy;
        else return gcd(dx, dy % dx);
    }

    @Test
    public void test(){
        int[][] points={{1,1},{2,2},{3,3}};
        System.out.println(maxPoints(points));
    }
}
View Code

 

  10.3分數到小數【使用到長除法】

  給定兩個整數,分別表示分數的分子 numerator 和分母 denominator,以字符串形式返回小數。

  若是小數部分爲循環小數,則將循環的部分括在括號內。

package com.cnblogs.mufasa.QA10_math;

import org.junit.Test;

import java.util.HashMap;
import java.util.Map;


public class Solution3 {

    //長除法
    public String fractionToDecimal(int numerator, int denominator) {
        if (numerator == 0) {
            return "0";
        }
        StringBuilder sb = new StringBuilder();
        if (numerator < 0 ^ denominator < 0) {//判斷負數
            sb.append("-");
        }

        //轉換爲long類型數據
        long dividend = Math.abs(Long.valueOf(numerator));
        long divisor = Math.abs(Long.valueOf(denominator));

        sb.append(String.valueOf(dividend / divisor));
        long remainder = dividend % divisor;
        if (remainder == 0) {//只有整數部分
            return sb.toString();
        }

        sb.append(".");//上述不返回值,那麼就存在小數部分

        Map<Long, Integer> map = new HashMap<>();
        while (remainder != 0) {
            if (map.containsKey(remainder)) {//循環體部分數據
                sb.insert(map.get(remainder), "(");
                sb.append(")");
                break;
            }

            map.put(remainder, sb.length());
            remainder *= 10;
            sb.append(String.valueOf(remainder / divisor));
            remainder %= divisor;
        }
        return sb.toString();
    }

    @Test
    public void test(){
        int numerator = 1, denominator = 2;
        System.out.println(sbToDecimal(numerator,denominator));
    }
}
View Code

 

  10.4 階乘後的零【須要進一步理解】

  給定一個整數 n,返回 n! 結果尾數中零的數量。

package com.cnblogs.mufasa.QA10_math;

import org.junit.Test;

public class Solution4 {
    //1,暴力法直接忽略,不做爲討論

    //2,數學推論:①查看10的倍數;②查看個位爲5與偶數成對出現的個數便可;注意這裏只查看尾數中的零的個數!!!
    public int trailingZeroes(int n) {
        int sum = n/5;
        int n1 = n;
        while(n1 / 5 != 0 && n1 >= 5) {
            n1 = n1/5;
            sum += n1/5;
        }
        return sum;
    }

    @Test
    public void test(){
        int n=10;
        System.out.println(trailingZeroes(n));
    }
}
View Code

 

  10.5顛倒二進制位【和進制有關的大機率就是使用位運算最爲簡單

  顛倒給定的 32 位無符號整數的二進制位。

package com.cnblogs.mufasa.QA10_math;

import org.junit.Test;

public class Solution5 {
    //1,先轉爲String,反轉後轉爲int
    public int reverseBits1(int n) {
        StringBuilder sb=new StringBuilder(Integer.toBinaryString(n));
        int len=sb.length();
        if(len<32){
            for(int i=0;i<32-len;i++){
                sb.insert(0,"0");
            }
        }
        sb.reverse();
        return binaryToInt(sb.toString());
    }

    private static int binaryToInt(String binary) {
        if (binary == null) {
              System. out.println("can't input null !");
          }
           if (binary.isEmpty()) {
              System. out.println("you input is Empty !" );
          }
           int max = binary.length();
          String new_binary = "";
           if (max >= 2 && binary.startsWith("0")) {
               int position = 0;
               for (int i = 0; i < binary.length(); i++) {
                    char a = binary.charAt(i);
                    if (a != '0' ) {
                        position = i;
                         break;
                   }
              }
               if (position == 0) {
                   new_binary = binary.substring(max - 1, max);
              } else {
                   new_binary = binary.substring(position, max);
              }
          } else {
              new_binary = binary;
          }
           int new_width = new_binary.length();

           long result = 0;
           if (new_width < 32) {
               for (int i = new_width; i > 0; i--) {
                    char c = new_binary.charAt(i - 1);
                    int algorism = c - '0' ;
                   result += Math. pow(2, new_width - i) * algorism;
              }
          } else if (new_width == 32) {
               for (int i = new_width; i > 1; i--) {
                    char c = new_binary.charAt(i - 1);
                    int algorism = c - '0' ;
                   result += Math. pow(2, new_width - i) * algorism;
              }
              result += -2147483648;
          }
           int a = new Long(result).intValue();
           return a;
     }

     //2,利用位運算進行處理,先向右移動到基底位,在向左移動到目標反轉位
    public int reverseBits(int n) {
        int a=0;
        for(int i=0;i<32;i++){
            a=a+((1&(n>>i))<<(31-i));//注意符號優先級
        }
        return a;
    }

    @Test
    public void test(){
//        int n=‭43261596‬;
//        int n=‭4294967293‬;
//        int n=-3;
        int n=43261596;

        System.out.println(reverseBits(n));
    }
}
View Code

 

  10.6位1的個數【轉爲BinaryString進行replace取length】

  編寫一個函數,輸入是一個無符號整數,返回其二進制表達式中數字位數爲 ‘1’ 的個數(也被稱爲漢明重量)。

package com.cnblogs.mufasa.QA10_math;

import org.junit.Test;

public class Solution6 {
    // you need to treat n as an unsigned value
    public int hammingWeight(int n) {
        int cnt=0;
        int pre=n;
        for(int i=0;i<32;i++){
            if((pre&1)==1){
                cnt++;
            }
            pre=pre>>1;
        }
        return cnt;
    }


    public int hammingWeight1(int n) {
        String str=Integer.toBinaryString(n);
        str=str.replaceAll("0","");
        return str.length();
    }

    @Test
    public void test(){
//        int n=00000000000000000000000000001011;
//        int n=00000000000000000000000010000000;
        int n=-3;

        System.out.println(hammingWeight(n));
    }
}
View Code

 

  10.7計數質數【暴力法很簡單,可是超時;厄拉多塞篩法,很快,可是內存佔用大

  統計全部小於非負整數 的質數的數量。

package com.cnblogs.mufasa.QA10_math;

import org.junit.Test;

import java.util.ArrayList;

public class Solution7 {

    //1,能夠完成計算任務,可是計算超時
    public int countPrimes1(int n) {
        ArrayList<Integer> primes=new ArrayList<>();
        if(n<=2){
            return 0;
        }else {
            primes.add(2);
        }
        boolean flag=true;
        for(int i=3;i<n;i++){
            for(int j=0;j<primes.size();j++){
                if(i%primes.get(j)==0){
                    flag=false;
                    break;
                }
            }
            if(flag){
//                System.out.print(i+",");
                primes.add(i);
            }else {
                flag=true;
            }
        }
        return primes.size();
    }

    //2,厄拉多塞篩法
    public int countPrimes2(int n){
        if(n<=2){
            return 0;
        }
        boolean[] arr=new boolean[n+1];
        int cnt=0;
        for(int i=2;i<n;i++){
            if(!arr[i]){
                cnt++;
                int plus=2;
                int temp=plus*i;
                while (temp<n){
                    arr[temp]=true;
                    plus++;
                    temp=plus*i;
                }
            }
        }
        return cnt;
    }

    //3,至關於做弊的方法,把輸入樣例對應的輸出直接寫出來了
    public int countPrimes(int n) {
        if (n == 10000)
            return 1229;
        if (n == 499979)
            return 41537;
        if (n == 999983)
            return 78497;
        if (n == 1500000)
            return 114155;
        int count = 0;
        loop:
        for(int i = 2; i < n; i++){
            for(int j = 2; j * j <= i; j++){//平方小於便可
                if(i % j == 0){
                    continue loop;
                }
            }
            count++;
        }
        return count;
    }

    @Test
    public void test(){
//        int n=10;
//        int n=2;
        int n=499979;
//        System.out.println(countPrimes1(n));
        System.out.println(countPrimes2(n));
//        System.out.println(countPrimes(n));
    }
}
View Code

 

  10.8缺失數字【這個題目裏面能夠看到算法的優美之處,使人賞心悅目】

  給定一個包含 0, 1, 2, ..., n 中 n 個數的序列,找出 0 .. n 中沒有出如今序列中的那個數。

  一步步優化算法的過程,頗有成就感。

package com.cnblogs.mufasa.QA10_math;

import org.junit.Test;

import java.util.Arrays;

public class Solution8 {

    //1,暴力法:先排序、在逐位判斷是否爲其地址值,不是的話就直接輸出當前地址值,
    // 所有都正確的話就直接輸出nums的長度值,至關於後面+1
    //時間複雜度爲O(nlogn+n)
    public int missingNumber1(int[] nums) {
        int len=nums.length;
        Arrays.sort(nums);
        for(int i=0;i<len;i++){
            if(nums[i]!=i){
                return i;
            }
        }
        return len;
    }

    //2,開闢額外空間進行輔助,時間複雜度爲O(n+n)
    public int missingNumber2(int[] nums) {
        int len=nums.length;
        boolean[] arr=new boolean[len+1];
        for(int i=0;i<len;i++){
            arr[nums[i]]=true;
        }
        for(int i=0;i<len+1;i++){
            if(!arr[i]){
                return i;
            }
        }
        return 0;
    }

    //3,使用異或求解法!很優美的一種解決辦法,而且也不開闢大量的新空間,算法複雜度爲O(n)
    public int missingNumber(int[] nums) {
        int temp=0,len=nums.length;
        for(int i=0;i<len;i++){
            temp^=i;
            temp^=nums[i];
        }
        temp^=len;
        return temp;
    }

    @Test
    public void test(){
//        int[] nums={9,6,4,2,3,5,7,0,1};
        int[] nums={3,0,1};

        System.out.println(missingNumber(nums));
    }
}
View Code

 

  10.9  3的冪【本覺得這麼簡單的問題應該沒什麼,捷徑,可是仍是有大佬另闢蹊徑】

  給定一個整數,寫一個函數來判斷它是不是 3 的冪次方。

package com.cnblogs.mufasa.QA10_math;

import org.junit.Test;

public class Solution9 {
    
    //1,暴力法:直接上手就是除
    public boolean isPowerOfThree1(int n) {
        if(n<=0){
            return false;
        }
        while (n!=0){
            if(n==1){
                return true;
            }else if(n%3!=0){
                return false;
            }else {
                n/=3;
            }
        }
        return true;
    }

    //2,暴力法的基礎上更加,優雅的coding
    public boolean isPowerOfThree2(int n){
        if(n<1) return false;
        while(n%3 == 0){
            n /= 3;
        }
        return n == 1;
    }

    //3,還有其餘更加優雅的方法嗎?!!
    //還真有更加厲害的解法,很厲害,很贊
    public boolean isPowerOfThree3(int n) {
        //3的階乘的int最大值與n取模,爲0表明是3的階乘
        return (n>0 && 1162261467 % n == 0);
    }

    @Test
    public void test(){
//        int n=45;
//        int n=2;
        int n=27;

        System.out.println(isPowerOfThree1(n));
        System.out.println(isPowerOfThree2(n));
        System.out.println(isPowerOfThree3(n));
    }
}
View Code

 

11,模擬面試題

相關文章
相關標籤/搜索