劍指offer面試題(41-50)——java實現

鈺鈺鈺鈺帝發表於2020-10-01

面試題41:和為s的兩個數字&和為s的連續整數序列

1.輸入一個遞增的陣列和一個數字s,在陣列中找到和為s的兩個數字
  如果有多對數字的和,輸出任意一對即可
public class TwoNumberSumIsS {
    public static void getTwoNumberSumIsS(int[] array, int s){
        if (array == null || array.length < 2) return;
        int low = 0, high = array.length - 1;
        while (low < high){
            int pre = array[low], after = array[high];
            if (pre + after == s){
                System.out.println(pre + "/" + after);
                return;
            }else if (pre + after < s) {
                low++;
            }else {
                high--;
            }
        }
        System.out.println("不存在和為s的");
    }

    public static void main(String[] args) {
        int[] array = {1,2,4,7,11,15};
        getTwoNumberSumIsS(array, 26);
    }
}

2.和為s的連續整數序列
public class SumIsS {
    public static void getSumIsS(int s){
        if (s < 3) return;
        int small = 1, big = 2, middle = (s + 1) >> 1, sum = 3;
        while (small < middle){
            if (sum == s){
                printResult(small, big);
            }
            while (sum > s && small < middle){
                sum -= small;
                small++;
                if (sum == s) printResult(small, big);
            }
            big++;
            sum += big;
        }
    }

    private static void printResult(int small, int big) {
        for (int i = small; i <= big; i++){
            System.out.printf("%d ", i);
        }
        System.out.println();
    }

    public static void main(String[] args) {
        getSumIsS(100);
    }
}

面試題42:翻轉單詞順序&左旋轉字串

1.輸入一個英文句子,翻轉句子中單詞的順序,但單詞內字元的順序不變,為簡單起見,標點符號和普通字元一樣處理。

思路:首先翻轉整個句子,然後再翻轉每個單詞的次序

public class Reverse {
    /**
     * 翻轉字串
     * @param str
     * @return
     */
    public static StringBuilder reverse(StringBuilder str){
        if (str == null || str.length() == 0) return null;
        int len = str.length();
        for (int i = 0, j = len - 1; i < j; i++, j--){
            char tempI = str.charAt(i);
            char tempJ = str.charAt(j);
            str.setCharAt(i , tempJ);
            str.setCharAt(j, tempI);
        }
        return str;
    }

    public static String reverse(String str){
        if (str == null || str.length() == 0) return null;
        StringBuilder temp = new StringBuilder(str);
        temp = reverse(temp);
//        記錄要翻轉的單詞
        StringBuilder word = new StringBuilder();
//        i是單詞的起始位置,j是結束為止
        for (int i = 0, j = 0; i < str.length() && j < str.length();){
            if (' ' == temp.charAt(j)){
                temp.replace(i, j, reverse(word).toString());
                i = j + 1;
                j++;
                word.delete(0,word.length());
            }else {
                word.append(temp.charAt(j));
                j++;
            }
        }
        return temp.toString();
    }

    public static void main(String[] args) {

        System.out.printf(reverse("i love you"));
    }
}

2.字串的左旋操作就是把字串前面的若干字元轉移到字串的尾部。

思路:前面若干字元pre取出,後面若干字元after取出,返回after+left
(其實是翻轉前半部分字元,再翻轉後半部分字元,然後翻轉整個字元)

public class LeftRotateString {
    public static String getLeftRotateString(String str, int number){
        if (str == null || str.length() == 0) return null;
        StringBuilder temp = new StringBuilder(str);
        StringBuilder word = new StringBuilder();
        for (int i = 0; i < number; i++){
            word.append(temp.charAt(i));
        }
        StringBuilder after = new StringBuilder();
        for (int i = number; i < str.length(); i++){
            after.append(temp.charAt(i));
        }
        after.append(word);
        return after.toString();
    }

    public static void main(String[] args) {
        System.out.print(getLeftRotateString("abcdef", 2));
    }
}

面試題43:n個骰子的點數

 把n個骰子扔在地上,所有骰子朝上的一面的點數之和為s
 輸入n,列印出s的所有可能的值出現的概率。

思路:用陣列存放每種骰子點數和出現的次數。令陣列中下標為n的元素存放點數和為n的次數。我們設定迴圈,每個迴圈多投擲一個骰子,假設某一輪迴圈中,我們已知了各種點數和出現的次數;在下一輪迴圈時,我們新投擲了一個骰子,那麼此時點數和為n的情況出現的次數就等於上一輪點數和為n-1,n-2,n-3,n-4,n-5,n-6的情況出現次數的總和。從第一個骰子開始,迴圈n次,就可以求得第n個骰子時各種點數和出現的次數。

我們這裡用兩個陣列來分別存放本輪迴圈與下一輪迴圈的各種點數和出現的次數,不斷交替使用。

public class NTouZiDeDianShu {
    /**
     *
     * @param n 骰子個數
     * @param count 骰子的最大點數
     */
    public static void getPropotity(int n, int count){
        if (n < 1) return;
        int[][] arrays = new int[2][n * count + 1];
        //[2]代表用兩個陣列交替儲存,[number*maxValue+1]是指點數為所在下標時,該點數出現的總次數。
        //arrays[*][0]是沒用的,只是為了讓下標對應點數
        for (int i = 0; i < n * count + 1; i++){
            arrays[0][i] = 0;
            arrays[1][i] = 0;
        }
        int flag = 0;
        for (int i = 1; i <= count; i++){
            arrays[flag][i] = 1;//第一個骰子出現的情況
        }
        for (int k = 2; k <= n; k++){//當前是第幾個骰子
            for (int i = 0; i <= k; i++){
                arrays[1- flag][i] = 0;//前面的資料清零
            }
            for (int i = k; i <= count * k; i++){
                for (int j = 1; j <= i && j <= count; j++){
                    arrays[1 - flag][i] += arrays[flag][i - j];
                }
            }
            flag = 1 - flag;
        }
        double total = Math.pow((double)count, n);
        for (int i = n; i <= count * n; i++){
            double ratio = (double)arrays[flag][i] / total;
            System.out.println(i + ":" + String.format("%.5f", ratio));
        }
    }

    public static void main(String[] args) {
        getPropotity(2,6);
    }
}

面試題44:撲克牌的順子

抽取5張牌,判斷是不是一個順子。
2-10為數字本身,A為1,J為11,Q為12,K為13,大小王可堪稱任意數字。
import java.util.Arrays;

public class Straight {
    public static boolean isStraight(int[] cards){
        if (cards == null || cards.length < 5) return false;
        boolean flag = true;
        Arrays.sort(cards);
        int zeroCount = 0, skipCount = 0;
        for (int i = 0; i < cards.length; i++){
            if (cards[i] == 0){//0表示大小王
                zeroCount++;
            }
        }
        int small = zeroCount, big = small + 1;//統計間隔數
        while (big < cards.length){
            if (cards[small] == cards[big]){
                flag = false;
                break;
            }
            skipCount += cards[big] - cards[small] - 1;
            small = big;
            big++;
        }
        if (skipCount > zeroCount) flag = false;
        return flag;
    }

    public static void main(String[] args) {
        int[] cards = {2,0,4,3,0};
        System.out.println(isStraight(cards));
    }
}

面試題45:圓圈中剩下的最後一個數字

0,1,,n-1這n個數字排成一個圓圈,從數字0開始,每次從這個圓圈裡刪除第m個數字
。求出這個圓圈裡剩下的最後一個數字。

例如,0、1、2、3、4這5個數字組成一個圓圈,從數字0開始每次刪除第3個數字,
則刪除的前4個數字依次是2、0、4、1,因此最後剩下的數字是3。

思路1:使用環形連結串列

    /**
     * 使用環形連結串列
     * @param circle 資料
     * @param m 第m個數
     * @return
     */
    public static int getTheLastNumberInCircle(int[] circle, int m){
        if (circle == null || circle.length == 0) return -1;
        List<Integer> loop = new ArrayList<Integer>();
        for (int i = 0; i < circle.length; i++){
            loop.add(circle[i]);
        }
        int n = circle.length;
        int i = m % n - 1;
        if (i == -1) i = loop.size() - 1;
        loop.remove(i);
        while (loop.size() != 1){
            int count = 0;
            if (count < m) {
                i++;
                i = i % loop.size();
            }else break;
            loop.remove(i);
        }
        int res = loop.get(0);
        return res;
    }

思路2:使用數學公式

    /**
     * @param circle 資料
     * @param m 第m個數
     * @return
     */
    public static int getTheLastNumberInCircle(int[] circle, int m){
        if (circle == null || circle.length == 0) return -1;
        int n = circle.length;
        if (n < 1 || m < 1) return -1;
        int last = 0;
        for (int i = 2; i <= n; i++) {
            last = (last + m) % i;
        }
        return circle[last];
    }

面試題46:求1+2+3+…+n

要求不能使用乘除法,for、while、if、else、switch、case以及條件判斷語句

思路:使用遞迴

public class PlusWithoutAnything {
    public static int getSum(int n) {
        int sum = n;
        boolean ans = (n > 0) && ((sum += getSum(n - 1)) > 0);
        return sum;
    }

    public static void main(String[] args) {
        System.out.println(getSum(100));
    }
}

面試題47:不用加減乘除做加法

public class SumOfTwoNumberWithoutAnything {

    public static int getSum(int a, int b){
        int sum, carry;
        do{
            sum = a ^ b;
            carry = (a & b) << 1;
            a = sum;
            b = carry;
        }while ( (b != 0));
        return sum;
    }

    public static void main(String[] args) {
        System.out.println(getSum(1,2));
    }
}

面試題48:不能繼承的類

//使用final關鍵字

面試題49:把字串轉換成數字

要考慮的東西很多,如:
1.字串為空時
2.有符號+、-時
3.非法輸入時
4.數字越界時
public class String2Number {

    public static int getNumber(String number){
        if (number == null || number.length() == 0) {
            System.out.println("字串為空!");
            return 0;
        }
        boolean minus = false;//是否是負數
        int index = 0;//字串開始的下標
        if (number.charAt(0) == '-') {
            index++;
            minus = true;
        }
        else if (number.charAt(0) == '+'){
            index++;
        }
        return getNumber(number, minus, index);
    }

    private static int getNumber(String number, boolean minus, int index) {
        int sign = 1;
        long result = 0;
        if (minus) sign = -1;
        for (int i = index; i < number.length(); i++){
            if (number.charAt(i) >= '0' && number.charAt(i) <= '9'){
                result = 10 * result + Integer.parseInt(String.valueOf(number.charAt(i)));
                if ((!minus && result > Integer.MAX_VALUE) || (!minus && result < Integer.MIN_VALUE)){
                    System.out.println("越界!");
                    return 0;
                }
            }else {
                System.out.println("非法輸入!");
                return 0;
            }
        }
        return (int) (sign * result);
    }

    public static void main(String[] args) {
        System.out.println(getNumber("1230"));
    }
}

面試題50:樹中兩個節點的最低公共祖先

1.如果是二叉搜尋樹;

思路:最低公共祖先一定是小於一個數且大於另一個數的。如果同時小於,則在右子樹中,如果同時大於則在左子樹中

public class LowestCommonNode {
    public static Node getLowestCommonNode(Node tree, Node node1, Node node2){
        if (tree == null) return null;
        if (tree.value > node1.value && tree.value < node2.value) return tree;
        else if (tree.value < node1.value && tree.value < node2.value) return getLowestCommonNode(tree.right, node1, node2);
        else return getLowestCommonNode(tree.left, node1, node2);
    }

    public static void main(String[] args) {
        Node node1 = new Node(1);
        Node node2 = new Node(2);
        Node node3 = new Node(3);
        Node node4 = new Node(4);
        Node node5 = new Node(5);
        node2.left = node1;
        node2.right = node4;
        node4.left = node3;
        node4.right = node5;
        Node result = getLowestCommonNode(node2, node3, node5);
        if (result != null){
            System.out.println(result.value);
        }else {
            System.out.println("沒找到!");
        }

    }
}
2.如果是普通的樹但是有指向父節點的指標;

思路:跟著父指標構造兩條連結串列,尋找兩條連結串列的第一個公共節點

面試題37:兩個連結串列的第一個公共節點

3.如果是普通的樹且沒有指向父節點的指標;

思路:使用輔助空間,來儲存到達兩個節點的連結串列,然後找到兩個連結串列的公共節點

import java.util.LinkedList;
class TreeNode1{
    TreeNode1 node;
    Object value;
    TreeNode1[] childs;

    @Override
    public String toString() {
        return "value=" + value;
    }
}
public class LowestCommonNode2 {
        //使用兩個連結串列來儲存根節點到所求節點的路徑
        static LinkedList list1 = new LinkedList();
        static LinkedList list2 = new LinkedList();

        public static void main(String args[]) {
            TreeNode1 A = new TreeNode1();
            A.value = 'A';
            TreeNode1 B = new TreeNode1();
            B.value = 'B';
            TreeNode1 C = new TreeNode1();
            C.value = 'C';
            TreeNode1 D = new TreeNode1();
            D.value = 'D';
            TreeNode1 E = new TreeNode1();
            E.value = 'E';
            TreeNode1 F = new TreeNode1();
            F.value = 'F';
            TreeNode1 G = new TreeNode1();
            G.value = 'G';
            TreeNode1 H = new TreeNode1();
            H.value = 'H';
            TreeNode1 I = new TreeNode1();
            I.value = 'I';
            TreeNode1 J = new TreeNode1();
            J.value = 'J';
            A.childs = new TreeNode1[] {B,C};
            B.childs = new TreeNode1[] {D,E};
            D.childs = new TreeNode1[] {F,G};
            E.childs = new TreeNode1[] {H,I,J};
        /*
                A
               / \
              B   C
             /  \
             D    E
           / \   / | \
          F   G  H I  J
         */

            //找F,H節點的LCA
            TreeNode1 lca = findLCA(F,H,A);
            System.out.println(lca);
        }
        
        private static TreeNode1 findLCA(TreeNode1 node1, TreeNode1 node2, TreeNode1 root) {
            getPathFromRootToNode(node1,root,list1);
            getPathFromRootToNode(node2,root,list2);
            //list1 : D -- B -- A
            //list2 : E -- B -- A

            //接下來遍歷兩個連結串列找到最近的公共節點
            int index = 0;
            int length1 = list1.size();
            int length2 = list2.size();
            int sub = length1 > length2 ? length1-length2 : length2-length1;
            if(length2 > length1) {
                LinkedList temp = list1;
                list1 = list2;
                list2 = temp;
            }
            while(index != length2 - 1) {
                if(((TreeNode1)list1.get(index + sub)).value == ((TreeNode1)list2.get(index)).value) {
                    return (TreeNode1)list2.get(index);
                }else {
                    index++;
                }
            }
            return null;
        }


        private static boolean getPathFromRootToNode(TreeNode1 node, TreeNode1 currentRoot, LinkedList list) {
            //找到就直接返回true
            if(node.value == currentRoot.value) {
                return true;
            }
            //找不到就將當前節點加入路徑,push是在連結串列的頭插入的,offer是尾部
            list.push(currentRoot);
            boolean found = false;
            TreeNode1[] childs = currentRoot.childs;
            if (childs != null && childs.length > 0) {
                //遍歷當前節點的所有子節點,在子節點裡邊找
                for (int i = 0; i < childs.length; i++) {
                    if (found) {
                        break;
                    } else {
                        found = getPathFromRootToNode(node, childs[i], list);
                    }
                }
            }
            //找不到就將當前節點從路徑中刪除,因為是遞迴,當遞迴回來到這裡的時候,當前節點一定是list的最後一個節點,即棧頂
            if(!found) {
                list.pop();
            }
            return found;
        }
}

相關文章