Java實現劍指offer

文章目錄

03_01_DuplicationInArray

找出數組中重複的數字

題目描述

在一個長度爲 n 的數組裏的所有數字都在 0n-1 的範圍內。數組中某些數字是重複的,但不知道有幾個數字重複了,也不知道每個數字重複了幾次。請找出數組中任意一個重複的數字。例如,如果輸入長度爲 7 的數組 {2, 3, 1, 0, 2, 5, 3},那麼對應的輸出是重複的數字 2 或者 3

解法

解法一

排序後,順序掃描,判斷是否有重複,時間複雜度爲 O(n²)

解法二

利用哈希表,遍歷數組,如果哈希表中沒有該元素,則存入哈希表中,否則返回重複的元素。時間複雜度爲 O(n),空間複雜度爲 O(n)

解法三

長度爲 n,元素的數值範圍也爲 n,如果沒有重複元素,那麼數組每個下標對應的值與下標相等。

從頭到尾遍歷數組,當掃描到下標 i 的數字 nums[i]

  • 如果等於 i,繼續向下掃描;
  • 如果不等於 i,拿它與第 nums[i] 個數進行比較,如果相等,說明有重複值,返回 nums[i]。如果不相等,就把第 i 個數 和第 nums[i] 個數交換。重複這個比較交換的過程。

此算法時間複雜度爲 O(n),因爲每個元素最多隻要兩次交換,就能確定位置。空間複雜度爲 O(1)


/**
 * @author bingo
 * @since 2018/10/27
 */

public class Solution {
    /**
     * 查找數組中的重複元素
     * @param numbers 數組
     * @param length 數組長度
     * @param duplication duplication[0]存儲重複元素
     * @return boolean
     */
    public boolean duplicate(int[] numbers, int length, int[] duplication) {
        if (numbers == null || length < 1) {
            return false;
        }
        for (int e : numbers) {
            if (e >= length) {
                return false;
            }
        }

        for (int i = 0; i < length; ++i) {
            while (numbers[i] != i) {
                if (numbers[i] == numbers[numbers[i]]) {
                    duplication[0] = numbers[i];
                    return true;
                }
                swap(numbers, i, numbers[i]);
            }
        }

        return false;
    }

    private void swap(int[] numbers, int i, int j) {
        int t = numbers[i];
        numbers[i] = numbers[j];
        numbers[j] = t;
    }
}

測試用例

  1. 長度爲 n 的數組中包含一個或多個重複的數字;
  2. 數組中不包含重複的數字;
  3. 無效測試輸入用例(輸入空指針;長度爲 n 的數組中包含 0~n-1 之外的數字)。

03_02_DuplicationInArrayNoEdit

不修改數組找出重複的數字

題目描述

在一個長度爲 n+1 的數組裏的所有數字都在 1n 的範圍內,所以數組中至少有一個數字是重複的。請找出數組中任意一個重複的數字,但不能修改輸入的數組。例如,如果輸入長度爲 8 的數組 {2, 3, 5, 4, 3, 2, 6, 7},那麼對應的輸出是重複的數字 2 或者 3

解法

解法一

創建長度爲 n+1 的輔助數組,把原數組的元素複製到輔助數組中。如果原數組被複制的數是 m,則放到輔助數組第 m 個位置。這樣很容易找出重複元素。空間複雜度爲 O(n)

解法二

數組元素的取值範圍是 [1, n],對該範圍對半劃分,分成 [1, middle], [middle+1, n]。計算數組中有多少個(count)元素落在 [1, middle] 區間內,如果 count 大於 middle-1+1,那麼說明這個範圍內有重複元素,否則在另一個範圍內。繼續對這個範圍對半劃分,繼續統計區間內元素數量。

時間複雜度 O(n * log n),空間複雜度 O(1)

注意,此方法無法找出所有重複的元素。

/**
 * @author bingo
 * @since 2018/10/27
 */

public class Solution {
    /**
     * 不修改數組查找重複的元素,沒有則返回-1
     * @param numbers 數組
     * @return 重複的元素
     */
    public int getDuplication(int[] numbers) {
        if (numbers == null || numbers.length < 1) {
            return -1;
        }

        int start = 1;
        int end = numbers.length - 1;
        while (end >= start) {
            int middle = start + ((end - start) >> 1);

            // 調用 log n 次
            int count = countRange(numbers, start, middle);
            if (start == end) {
                if (count > 1) {
                    return start;
                }
                break;
            } else {
                // 無法找出所有重複的數
                if (count > (middle - start) + 1) {
                    end = middle;
                } else {
                    start = middle + 1;
                }
            }
        }
        return -1;
    }


    /**
     * 計算整個數組中有多少個數的取值在[start, end] 之間
     * 時間複雜度 O(n)
     * @param numbers 數組
     * @param start 左邊界
     * @param end 右邊界
     * @return 數量
     */
    private int countRange(int[] numbers, int start, int end) {
        if (numbers == null) {
            return 0;
        }
        int count = 0;
        for(int e : numbers) {
            if (e >= start && e <= end) {
                ++count;
            }
        }
        return count;
    }
}

測試用例

  1. 長度爲 n 的數組中包含一個或多個重複的數字;
  2. 數組中不包含重複的數字;
  3. 無效測試輸入用例(輸入空指針)。

04_FindInPartiallySortedMatrix

二維數組中的查找

題目描述

在一個二維數組中(每個一維數組的長度相同),每一行都按照從左到右遞增的順序排序,每一列都按照從上到下遞增的順序排序。請完成一個函數,輸入這樣的一個二維數組和一個整數,判斷數組中是否含有該整數。

解法

從二維數組的右上方開始查找:

  • 若元素值等於 target,返回 true
  • 若元素值大於 target,砍掉這一列,即 --j
  • 若元素值小於 target,砍掉這一行,即 ++i

也可以從二維數組的左下方開始查找,以下代碼使用左下方作爲查找的起點。

注意,不能選擇左上方或者右下方的數字,因爲這樣無法縮小查找的範圍。

/**
 * @author bingo
 * @since 2018/10/27
 */

public class Solution {
    /**
     * 二維數組中的查找
     * @param target 目標值
     * @param array 二維數組
     * @return boolean
     */
    public boolean find(int target, int[][] array) {
        if (array == null) {
            return false;
        }
        int rows = array.length;
        int columns = array[0].length;
        
        int i = rows - 1;
        int j = 0;
        while (i >= 0 && j < columns) {
            if (array[i][j] == target) {
                return true;
            }
            if (array[i][j] < target) {
                ++j;
            } else {
                --i;
            }
        }
        return false;
    }
}

測試用例

  1. 二維數組中包含查找的數字(查找的數字是數組中的最大值和最小值;查找的數字介於數組中的最大值和最小值之間);
  2. 二維數組中沒有查找的數字(查找的數字大於/小於數組中的最大值;查找的數字在數組的最大值和最小值之間但數組中沒有這個數字);
  3. 特殊輸入測試(輸入空指針)。

05_ReplaceSpaces

替換空格

題目描述

請實現一個函數,將一個字符串中的每個空格替換成 %20。例如,當字符串爲 We Are Happy,則經過替換之後的字符串爲 We%20Are%20Happy

解法

解法一

創建 StringBuilder,遍歷原字符串,遇到非空格,直接 append 到 StringBuilder 中,遇到空格則將 %20 append 到 StringBuilder 中。

/**
 * @author bingo
 * @since 2018/10/27
 */

public class Solution {
    /**
     * 將字符串中的所有空格替換爲%20
     * @param str 字符串
     * @return 替換後的字符串
     */
    public String replaceSpace(StringBuffer str) {
        if (str == null || str.length() == 0) {
            return str.toString();
        }
        StringBuilder sb = new StringBuilder();
        int len = str.length();
        for (int i = 0; i < len; ++i) {
            char ch = str.charAt(i);
            sb.append(ch == ' ' ? "%20" : ch);
        }

        return sb.toString();
    }
}

解法二【推薦】

先遍歷原字符串,遇到空格,則在原字符串末尾 append 任意兩個字符,如兩個空格。

用指針 p 指向原字符串末尾,q 指向現字符串末尾,p, q 從後往前遍歷,當 p 遇到空格,q 位置依次要 append ‘02%’,若不是空格,直接 append p 指向的字符。

?思路擴展:
在合併兩個數組(包括字符串)時,如果從前往後複製每個數字(或字符)需要重複移動數字(或字符)多次,那麼我們可以考慮從後往前複製,這樣就能減少移動的次數,從而提高效率。

/**
 * @author bingo
 * @since 2018/10/27
 */

public class Solution {
    /**
     * 將字符串中的所有空格替換爲%20
     * @param str 字符串
     * @return 替換後的字符串
     */
    public String replaceSpace(StringBuffer str) {
        if (str == null || str.length() == 0) {
            return str.toString();
        }
        
        int len = str.length();
        for (int i = 0; i < len; ++i) {
            if (str.charAt(i) == ' ') {
                // append 兩個空格
                str.append("  ");
            }
        }

        // p 指向原字符串末尾
        int p = len - 1;

        // q 指向現字符串末尾
        int q = str.length() - 1;

        while (p >= 0) {
            char ch = str.charAt(p--);
            if (ch == ' ') {
                str.setCharAt(q--, '0');
                str.setCharAt(q--, '2');
                str.setCharAt(q--, '%');
            } else {
                str.setCharAt(q--, ch);
            }
        }

        return str.toString();

    }
}

測試用例

  1. 輸入的字符串包含空格(空格位於字符串的最前面/最後面/中間;字符串有多個連續的空格);
  2. 輸入的字符串中沒有空格;
  3. 特殊輸入測試(字符串是一個空指針;字符串是一個空字符串;字符串只有一個空格字符;字符串中有多個連續空格)。

06_PrintListInReversedOrder

從尾到頭打印鏈表

題目描述

輸入一個鏈表,按鏈表值從尾到頭的順序返回一個 ArrayList

解法

解法一【推薦】

遍歷鏈表,每個鏈表結點值 push 進棧,最後將棧中元素依次 poplist 中。

/**
*    public class ListNode {
*        int val;
*        ListNode next = null;
*
*        ListNode(int val) {
*            this.val = val;
*        }
*    }
*
*/
import java.util.ArrayList;
import java.util.Stack;

/**
 * @author bingo
 * @since 2018/10/28
 */
public class Solution {
    /**
     * 從尾到頭打印鏈表
     * @param listNode 鏈表頭節點
     * @return list
     */
    public ArrayList<Integer> printListFromTailToHead(ListNode listNode) {
        ArrayList<Integer> res = new ArrayList<>();
        if (listNode == null) {
            return res;
        }
        Stack<Integer> stack = new Stack<>();
        while (listNode != null) {
            stack.push(listNode.val);
            listNode = listNode.next;
        }
        while (!stack.isEmpty()) {
            res.add(stack.pop());
        }
        
        return res;
    }
}

解法二【不推薦】

利用遞歸方式:

  • 若不是鏈表尾結點,繼續遞歸;
  • 若是,添加到 list 中。

這種方式不推薦,當遞歸層數過多時,容易發生 Stack Overflow

/**
*    public class ListNode {
*        int val;
*        ListNode next = null;
*
*        ListNode(int val) {
*            this.val = val;
*        }
*    }
*
*/
import java.util.ArrayList;
import java.util.Stack;

/**
 * @author bingo
 * @since 2018/10/28
 */
public class Solution {
    /**
     * 從尾到頭打印鏈表
     * @param listNode 鏈表頭結點
     * @return list
     */
    public ArrayList<Integer> printListFromTailToHead(ListNode listNode) {
        ArrayList<Integer> res = new ArrayList<>();
        if (listNode == null) {
            return res;
        }
        
        addElement(listNode, res);
        return res;
        
    }
    
    private void addElement(ListNode listNode, ArrayList<Integer> res) {
        if (listNode.next != null) {
            // 遞歸調用
            addElement(listNode.next, res);
        }
        res.add(listNode.val);
    }
}

測試用例

  1. 功能測試(輸入的鏈表有多個結點;輸入的鏈表只有一個結點);
  2. 特殊輸入測試(輸入的鏈表結點指針爲空)。

07_ConstructBinaryTree

重建二叉樹

題目描述

輸入某二叉樹的前序遍歷和中序遍歷的結果,請重建出該二叉樹。假設輸入的前序遍歷和中序遍歷的結果中都不含重複的數字。例如輸入前序遍歷序列 {1,2,4,7,3,5,6,8} 和中序遍歷序列 {4,7,2,1,5,3,8,6},則重建二叉樹並返回。

解法

在二叉樹的前序遍歷序列中,第一個數字總是根結點的值。在中序遍歷序列中,根結點的值在序列的中間,左子樹的結點位於根結點左側,而右子樹的結點位於根結點值的右側。

遍歷中序序列,找到根結點,遞歸構建左子樹與右子樹。

注意添加特殊情況的 if 判斷。

/**
 * Definition for binary tree
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */

/**
 * @author bingo
 * @since 2018/10/28
 */

public class Solution {
    /**
     * 重建二叉樹
     * 
     * @param pre 先序序列
     * @param in  中序序列
     * @return 二叉樹根結點
     */
    public TreeNode reConstructBinaryTree(int[] pre, int[] in) {
        if (pre == null || in == null || pre.length != in.length) {
            return null;
        }
        int n = pre.length;
        return constructBinaryTree(pre, 0, n - 1, in, 0, n - 1);
    }

    private TreeNode constructBinaryTree(int[] pre, int startPre, int endPre, int[] in, int startIn, int endIn) {
        TreeNode node = new TreeNode(pre[startPre]);
        if (startPre == endPre) {
            if (startIn == endIn) {
                return node;
            }
            throw new IllegalArgumentException("Invalid input!");
        }

        int inOrder = startIn;
        while (in[inOrder] != pre[startPre]) {
            ++inOrder;
            if (inOrder > endIn) {
                new IllegalArgumentException("Invalid input!");
            }
        }
        int len = inOrder - startIn;
        if (len > 0) {
            // 遞歸構建左子樹
            node.left = constructBinaryTree(pre, startPre + 1, startPre + len, in, startIn, inOrder - 1);
        }

        if (inOrder < endIn) {
            // 遞歸構建右子樹
            node.right = constructBinaryTree(pre, startPre + len + 1, endPre, in, inOrder + 1, endIn);
        }
        return node;

    }
}

測試用例

  1. 普通二叉樹(完全二叉樹;不完全二叉樹);
  2. 特殊二叉樹(所有結點都沒有左/右子結點;只有一個結點的二叉樹);
  3. 特殊輸入測試(二叉樹根結點爲空;輸入的前序序列和中序序列不匹配)。

08_NextNodeInBinaryTrees

二叉樹的下一個結點

題目描述

給定一個二叉樹和其中的一個結點,請找出中序遍歷順序的下一個結點並且返回。注意,樹中的結點不僅包含左右子結點,同時包含指向父結點的指針。

解法

對於結點 pNode

  • 如果它有右子樹,則右子樹的最左結點就是它的下一個結點;
  • 如果它沒有右子樹,判斷它與父結點 pNode.next 的位置情況:
    • 如果它是父結點的左孩子,那麼父結點 pNode.next 就是它的下一個結點;
    • 如果它是父結點的右孩子,一直向上尋找,直到找到某個結點,它是它父結點的左孩子,那麼該父結點就是 pNode 的下一個結點。
/*
public class TreeLinkNode {
    int val;
    TreeLinkNode left = null;
    TreeLinkNode right = null;
    TreeLinkNode next = null;

    TreeLinkNode(int val) {
        this.val = val;
    }
}
*/

/**
 * @author bingo
 * @since 2018/10/28
 */

public class Solution {
    /**
     * 獲取中序遍歷結點的下一個結點
     * @param pNode 某個結點
     * @return pNode的下一個結點
     */
    public TreeLinkNode GetNext(TreeLinkNode pNode) {
        if (pNode == null) {
            return null;
        }
        
        if (pNode.right != null) {
            TreeLinkNode t = pNode.right;
            while (t.left != null) {
                t = t.left;
            }
            return t;
        }
        
        // 須保證 pNode.next 不爲空,否則會出現 NPE
        if (pNode.next != null && pNode.next.left == pNode) {
            return pNode.next;
        }
        
        while (pNode.next != null) {
            if (pNode.next.left == pNode) {
                return pNode.next;
            }
            pNode = pNode.next;
        }
        
        return null;
        
    }
}

測試用例

  1. 普通二叉樹(完全二叉樹;不完全二叉樹);
  2. 特殊二叉樹(所有結點都沒有左/右子結點;只有一個結點的二叉樹;二叉樹的根結點爲空);
  3. 不同位置的結點的下一個結點(下一個結點爲當前結點的右子結點、右子樹的最左子結點、父結點、跨層的父結點等;當前結點沒有下一個結點)。

09_01_QueueWithTwoStacks

用兩個棧實現隊列

題目描述

用兩個棧來實現一個隊列,完成隊列的 PushPop 操作。 隊列中的元素爲 int 類型。

解法

Push 操作,每次都存入 stack1
Pop 操作,每次從 stack2 取:

  • stack2 棧不爲空時,不能將 stack1 元素倒入;
  • stack2 棧爲空時,需要一次將 stack1 元素全部倒入。
import java.util.Stack;

/**
 * @author bingo
 * @since 2018/10/28
 */

public class Solution {
    Stack<Integer> stack1 = new Stack<Integer>();
    Stack<Integer> stack2 = new Stack<Integer>();
    
    public void push(int node) {
        stack1.push(node);
    }
    
    public int pop() {
        if (stack2.isEmpty()) {
            if (stack1.isEmpty()) {
                return -1;
            }
            while (!stack1.isEmpty()) {
                stack2.push(stack1.pop());
            }
        }
        return stack2.pop();
    }
}

測試用例

  1. 往空的隊列裏添加、刪除元素;
  2. 往非空的隊列添加、刪除元素;
  3. 連續刪除元素直至隊列爲空。

09_02_StackWithTwoQueues

用兩個隊列實現棧

題目描述

用兩個隊列來實現一個棧,完成棧的 PushPop 操作。 棧中的元素爲 int 類型。

解法

Push 操作,每次都存入 queue1
Pop 操作,每次從 queue1 取:

  • queue1 中的元素依次倒入 queue2,直到 queue1 剩下一個元素,這個元素就是要 pop 出去的;
  • queue1queue2 進行交換,這樣保證每次都從 queue1 中存取元素,queue2 只起到輔助暫存的作用。
import java.util.LinkedList;
import java.util.Queue;

/**
 * @author bingo
 * @since 2018/10/29
 */

public class Solution {

    private Queue<Integer> queue1 = new LinkedList<>();
    private Queue<Integer> queue2 = new LinkedList<>();

    public void push(int node) {
        queue1.offer(node);
    }

    public int pop() {
        if (queue1.isEmpty()) {
            throw new RuntimeException("Empty stack!");
        }

        while (queue1.size() > 1) {
            queue2.offer(queue1.poll());
        }

        int val = queue1.poll();

        Queue<Integer> t = queue1;
        queue1 = queue2;
        queue2 = t;
        return val;

    }
}

測試用例

  1. 往空的棧裏添加、刪除元素;
  2. 往非空的棧添加、刪除元素;
  3. 連續刪除元素直至棧爲空。

10_01_Fibonacci

斐波那契數列

題目描述

大家都知道斐波那契數列,現在要求輸入一個整數n,請你輸出斐波那契數列的第 n 項(從 0 開始,第 0 項爲 0)。n<=39

解法

解法一

採用遞歸方式,簡潔明瞭,但效率很低,存在大量的重複計算。

                  f(10)
               /        \
            f(9)         f(8)
          /     \       /    \
       f(8)     f(7)  f(7)   f(6)
      /   \     /   \ 
   f(7)  f(6)  f(6) f(5)

/**
 * @author bingo
 * @since 2018/10/29
 */

public class Solution {
    /**
     * 求斐波那契數列的第n項,n從0開始
     * @param n 第n項
     * @return 第n項的值
     */
    public int Fibonacci(int n) {
        if (n < 2) {
            return n;
        }
        // 遞歸調用
        return Fibonacci(n - 1) + Fibonacci(n - 2);
    }
}

解法二

從下往上計算,遞推,時間複雜度 O(n)


/**
 * @author bingo
 * @since 2018/10/29
 */

public class Solution {
    /**
     * 求斐波那契數列的第n項,n從0開始
     * @param n 第n項
     * @return 第n項的值
     */
    public int Fibonacci(int n) {
        if (n < 2) {
            return n;
        }
        int[] res = new int[n + 1];
        res[0] = 0;
        res[1] = 1;
        for (int i = 2; i <= n; ++i) {
            res[i] = res[i - 1] + res[i - 2];
        }
        return res[n];

    }
}

測試用例

  1. 功能測試(如輸入 3、5、10 等);
  2. 邊界值測試(如輸入 0、1、2);
  3. 性能測試(輸入較大的數字,如 40、50、100 等)。

10_02_JumpFloor

跳臺階

題目描述

一隻青蛙一次可以跳上1級臺階,也可以跳上2級。求該青蛙跳上一個n級的臺階總共有多少種跳法(先後次序不同算不同的結果)。

解法

跳上 n 級臺階,可以從 n-1 級跳 1 級上去,也可以從 n-2 級跳 2 級上去。所以

f(n) = f(n-1) + f(n-2)
/**
 * @author bingo
 * @since 2018/11/23
 */

public class Solution {
    /**
     * 青蛙跳臺階
     * @param target 跳上的那一級臺階
     * @return 多少種跳法
     */
    public int JumpFloor(int target) {
        if (target < 3) {
            return target;
        }
        int[] res = new int[target + 1];
        res[1] = 1;
        res[2] = 2;
        for (int i = 3; i <= target; ++i) {
            res[i] = res[i - 1] + res[i - 2];
        }
        return res[target];
    }
}

測試用例

  1. 功能測試(如輸入 3、5、10 等);
  2. 邊界值測試(如輸入 0、1、2);
  3. 性能測試(輸入較大的數字,如 40、50、100 等)。

10_03_JumpFloorII

變態跳臺階

題目描述

一隻青蛙一次可以跳上1級臺階,也可以跳上2級……它也可以跳上n級。求該青蛙跳上一個n級的臺階總共有多少種跳法。

解法

跳上 n-1 級臺階,可以從 n-2 級跳 1 級上去,也可以從 n-3 級跳 2 級上去…也可以從 0 級跳上去。那麼

f(n-1) = f(0) + f(1) + ... + f(n-2) ①

跳上 n 級臺階,可以從 n-1 級跳 1 級上去,也可以從 n-2 級跳 2 級上去…也可以從 0 級跳上去。那麼

f(n) = f(0) + f(1) + ... + f(n-2) + f(n-1)  ②

②-①:
f(n) - f(n-1) = f(n-1)
f(n) = 2f(n-1)

所以 f(n) 是一個等比數列:

f(n) = 2^(n-1)
/**
 * @author bingo
 * @since 2018/11/23
 */

public class Solution {
    /**
     * 青蛙跳臺階II
     * @param target 跳上的那一級臺階
     * @return 多少種跳法
     */
    public int JumpFloorII(int target) {
        return (int) Math.pow(2, target - 1);
    }
}

測試用例

  1. 功能測試(如輸入 3、5、10 等);
  2. 邊界值測試(如輸入 0、1、2);
  3. 性能測試(輸入較大的數字,如 40、50、100 等)。

10_04_RectCover

矩形覆蓋

題目描述

我們可以用2*1的小矩形橫着或者豎着去覆蓋更大的矩形。請問用n2*1的小矩形無重疊地覆蓋一個2*n的大矩形,總共有多少種方法?

解法

覆蓋 2*n 的矩形:

  • 可以先覆蓋 2*n-1 的矩形,再覆蓋一個 2*1 的矩形;
  • 也可以先覆蓋 2*(n-2) 的矩形,再覆蓋兩個 1*2 的矩形。

解法一:利用數組存放結果

/**
 * @author bingo
 * @since 2018/11/23
 */

public class Solution {
    /**
     * 矩形覆蓋
     * @param target 2*target大小的矩形
     * @return 多少種覆蓋方法
     */
    public int RectCover(int target) {
        if (target < 3) {
            return target;
        }
        int[] res = new int[target + 1];
        res[1] = 1;
        res[2] = 2;
        for (int i = 3; i <= target; ++i) {
            res[i] = res[i - 1] + res[i - 2];
        }
        return res[target];
    }
}

解法二:直接用變量存儲結果

/**
 * @author bingo
 * @since 2018/11/23
 */

public class Solution {
    /**
     * 矩形覆蓋
     * @param target 2*target大小的矩形
     * @return 多少種覆蓋方法
     */
    public int RectCover(int target) {
        if (target < 3) {
            return target;
        }
        int res1 = 1;
        int res2 = 2;
        int res = 0;
        for (int i = 3; i <= target; ++i) {
            res = res1 + res2;
            res1 = res2;
            res2 = res;
        }
        return res;
    }
}

測試用例

  1. 功能測試(如輸入 3、5、10 等);
  2. 邊界值測試(如輸入 0、1、2);
  3. 性能測試(輸入較大的數字,如 40、50、100 等)。

11_MinNumberInRotatedArray

旋轉數組的最小數字

題目描述

把一個數組最開始的若干個元素搬到數組的末尾,我們稱之爲數組的旋轉。 輸入一個非減排序的數組的一個旋轉,輸出旋轉數組的最小元素。 例如數組 {3,4,5,1,2}{1,2,3,4,5} 的一個旋轉,該數組的最小值爲 1

**NOTE:**給出的所有元素都大於 0,若數組大小爲 0,請返回 0

解法

解法一

直接遍歷數組找最小值,時間複雜度 O(n),不推薦。


/**
 * @author bingo
 * @since 2018/10/30
 */

public class Solution {
    /**
     * 獲取旋轉數組的最小元素
     * @param array 旋轉數組
     * @return 數組中的最小值
     */
    public int minNumberInRotateArray(int[] array) {
        if (array == null || array.length == 0) {
            return 0;
        }

        int n = array.length;
        if (n == 1 || array[0] < array[n - 1]) {
            return array[0];
        }

        int min = array[0];
        for (int i = 1; i < n; ++i) {
            min = array[i] < min ? array[i] : min;
        }

        return min;
    }

}

解法二

利用指針 p,q 指向數組的首尾,如果 array[p] < array[q],說明數組是遞增數組,直接返回 array[p]。否則進行如下討論。

計算中間指針 mid

  • 如果此時 array[p], array[q], array[mid] 兩兩相等,此時無法採用二分方式,只能通過遍歷區間 [p,q] 獲取最小值;
  • 如果此時 p,q 相鄰,說明此時 q 指向的元素是最小值,返回 array[q]
  • 如果此時 array[mid] >= array[p],說明 mid 位於左邊的遞增數組中,最小值在右邊,因此,把 p 指向 mid,此時保持了 p 指向左邊遞增子數組;
  • 如果此時 array[mid] <= array[q],說明 mid 位於右邊的遞增數組中,最小值在左邊,因此,把 q 指向 mid,此時保持了 q 指向右邊遞增子數組。


/**
 * @author bingo
 * @since 2018/10/30
 */

public class Solution {
    /**
     * 獲取旋轉數組的最小元素
     * @param array 旋轉數組
     * @return 數組中的最小值
     */
    public int minNumberInRotateArray(int[] array) {
        if (array == null || array.length == 0) {
            return 0;
        }

        int p = 0;
        // mid初始爲p,爲了兼容當數組是遞增數組(即不滿足 array[p] >= array[q])時,返回 array[p]
        int mid = p;
        int q = array.length - 1;
        while (array[p] >= array[q]) {
            if (q - p == 1) {
                // 當p,q相鄰時(距離爲1),那麼q指向的元素就是最小值
                mid = q;
                break;
            }
            mid = p + ((q - p) >> 1);

            // 當p,q,mid指向的值相等時,此時只能通過遍歷查找最小值
            if (array[p] == array[q] && array[mid] == array[p]) {
                mid = getMinIndex(array, p, q);
                break;
            }

            if (array[mid] >= array[p]) {
                p = mid;
            } else if (array[mid] <= array[q]) {
                q = mid;
            }
        }

        return array[mid];


    }

    private int getMinIndex(int[] array, int p, int q) {
        int minIndex = p;
        for (int i = p + 1; i <= q; ++i) {
            minIndex = array[i] < array[minIndex] ? i : minIndex;
        }
        return minIndex;
    }
}

測試用例

  1. 功能測試(輸入的數組是升序排序數組的一個旋轉,數組中有重複數字或者沒有重複數字);
  2. 邊界值測試(輸入的數組是一個升序排序的數組,只包含一個數字的數組);
  3. 特殊輸入測試(輸入空指針)。

12_StringPathInMatrix

矩陣中的路徑

題目描述

請設計一個函數,用來判斷在一個矩陣中是否存在一條包含某字符串所有字符的路徑。路徑可以從矩陣中的任意一個格子開始,每一步可以在矩陣中向左,向右,向上,向下移動一個格子。如果一條路徑經過了矩陣中的某一個格子,則之後不能再次進入這個格子。 例如 a b c e s f c s a d e e 這樣的 3 X 4 矩陣中包含一條字符串"bcced"的路徑,但是矩陣中不包含"abcb"路徑,因爲字符串的第一個字符b佔據了矩陣中的第一行第二個格子之後,路徑不能再次進入該格子。

解法

回溯法。首先,任選一個格子作爲路徑起點。假設格子對應的字符爲 ch,並且對應路徑上的第 i 個字符。若相等,到相鄰格子尋找路徑上的第 i+1 個字符。重複這一過程。

/**
 * @author bingo
 * @since 2018/11/20
 */

public class Solution {
    /**
     * 判斷矩陣中是否包含某條路徑
     * @param matrix 矩陣
     * @param rows 行數
     * @param cols 列數
     * @param str 路徑
     * @return bool
     */
    public boolean hasPath(char[] matrix, int rows, int cols, char[] str) {
        if (matrix == null || rows < 1 || cols < 1 || str == null) {
            return false;
        }
        boolean[] visited = new boolean[matrix.length];
        int pathLength = 0;
        for (int i = 0; i < rows; ++i) {
            for (int j = 0; j < cols; ++j) {
                if (hasPath(matrix, rows, cols, str, i, j, pathLength, visited)) {
                    return true;
                }
            }
        }
        return false;
    }

    private boolean hasPath(char[] matrix, int rows, int cols, char[] str, int i, int j, int pathLength, boolean[] visited) {
        if (pathLength == str.length) {
            return true;
        }
        boolean hasPath = false;
        if (i >= 0 && i < rows && j >= 0 && j < cols && matrix[i * cols + j] == str[pathLength] && !visited[i * cols + j]) {
            ++pathLength;
            visited[i * cols + j] = true;
            hasPath = hasPath(matrix, rows, cols, str, i - 1, j, pathLength, visited)
                    || hasPath(matrix, rows, cols, str, i + 1, j, pathLength, visited)
                    || hasPath(matrix, rows, cols, str, i, j - 1, pathLength, visited)
                    || hasPath(matrix, rows, cols, str, i, j + 1, pathLength, visited);
            if (!hasPath) {
                --pathLength;
                visited[i * cols + j] = false;
            }
        }
        return hasPath;
    }
}

測試用例

  1. 功能測試(在多行多列的矩陣中存在或者不存在路徑);
  2. 邊界值測試(矩陣只有一行或者一列;矩陣和路徑中的所有字母都是相同的);
  3. 特殊輸入測試(輸入空指針)。

13_RobotMove

機器人的移動範圍

題目描述

地上有一個m行和n列的方格。一個機器人從座標0,0的格子開始移動,每一次只能向左,右,上,下四個方向移動一格,但是不能進入行座標和列座標的數位之和大於k的格子。 例如,當k18時,機器人能夠進入方格(35,37),因爲3+5+3+7 = 18。但是,它不能進入方格(35,38),因爲3+5+3+8 = 19。請問該機器人能夠達到多少個格子?

解法

從座標(0, 0) 開始移動,當它準備進入座標(i, j),判斷是否能進入,如果能,再判斷它能否進入 4 個相鄰的格子 (i-1, j), (i+1, j), (i, j-1), (i, j+1)。

/**
 * @author bingo
 * @since 2018/11/20
 */

public class Solution {
    /**
     * 計算能到達的格子數
     * @param threshold 限定的數字
     * @param rows 行數
     * @param cols 列數
     * @return 格子數
     */
    public int movingCount(int threshold, int rows, int cols) {
        if (threshold < 0 || rows < 1 || cols < 1) {
            return 0;
        }
        boolean[] visited = new boolean[rows * cols];
        return getCount(threshold, 0, 0, rows, cols, visited);
    }

    private int getCount(int threshold, int i, int j, int rows, int cols, boolean[] visited) {
        if (check(threshold, i, j, rows, cols, visited)) {
            visited[i * cols + j] = true;
            return 1
                    + getCount(threshold, i - 1, j, rows, cols, visited)
                    + getCount(threshold, i + 1, j, rows, cols, visited)
                    + getCount(threshold, i, j - 1, rows, cols, visited)
                    + getCount(threshold, i, j + 1, rows, cols, visited);
        }
        return 0;
    }

    private boolean check(int threshold, int i, int j, int rows, int cols, boolean[] visited) {
        return i >= 0
                && i < rows
                && j >= 0
                && j < cols
                && !visited[i * cols + j]
                && getDigitSum(i) + getDigitSum(j) <= threshold;
    }

    private int getDigitSum(int i) {
        int res = 0;
        while (i > 0) {
            res += i % 10;
            i /= 10;
        }
        return res;
    }
}

測試用例

  1. 功能測試(方格爲多行多列;k 爲正數);
  2. 邊界值測試(方格只有一行或者一列;k = 0);
  3. 特殊輸入測試(k < 0)。

14_CuttingRope

剪繩子

題目描述

給你一根長度爲n繩子,請把繩子剪成m段(mn都是整數,n>1並且m≥1)。每段的繩子的長度記爲k[0]、k[1]、……、k[m]。k[0]k[1]…*k[m]可能的最大乘積是多少?例如當繩子的長度是 8 時,我們把它剪成長度分別爲 2、3、3 的三段,此時得到最大的乘積18

解法

解法一:動態規劃法

時間複雜度O(n²),空間複雜度O(n)

  • 長度爲 2,只可能剪成長度爲 1 的兩段,因此 f(2)=1
  • 長度爲 3,剪成長度分別爲 1 和 2 的兩段,乘積比較大,因此 f(3) = 2
  • 長度爲 n,在剪第一刀的時候,有 n-1 種可能的選擇,剪出來的繩子又可以繼續剪,可以看出,原問題可以劃分爲子問題,子問題又有重複子問題。
/**
 * @author bingo
 * @since 2018/11/20
 */

public class Solution {

    /**
     * 剪繩子求最大乘積
     * @param length 繩子長度
     * @return 乘積最大值
     */
    public int maxProductAfterCutting(int length) {
        if (length < 2) {
            return 0;
        }
        if (length < 4) {
            return length - 1;
        }

        // res[i] 表示當長度爲i時的最大乘積
        int[] res = new int[length + 1];
        res[1] = 1;
        res[2] = 2;
        res[3] = 3;
        // 從長度爲4開始計算
        for (int i = 4; i <= length; ++i) {
            int max = 0;
            for (int j = 1; j <= i / 2; ++j) {
                max = Math.max(max, res[j] * res[i - j]);
            }
            res[i] = max;
        }

        return res[length];

    }
}

貪心算法

時間複雜度O(1),空間複雜度O(1)

貪心策略:

  • 當 n>=5 時,儘可能多地剪長度爲 3 的繩子
  • 當剩下的繩子長度爲 4 時,就把繩子剪成兩段長度爲 2 的繩子。

證明:

  • 當 n>=5 時,可以證明 2(n-2)>n,並且 3(n-3)>n。也就是說,當繩子剩下長度大於或者等於 5 的時候,可以把它剪成長度爲 3 或者 2 的繩子段。
  • 當 n>=5 時,3(n-3)>=2(n-2),因此,應該儘可能多地剪長度爲 3 的繩子段。
  • 當 n=4 時,剪成兩根長度爲 2 的繩子,其實沒必要剪,只是題目的要求是至少要剪一刀。
/**
 * @author bingo
 * @since 2018/11/20
 */

public class Solution {

    /**
     * 剪繩子求最大乘積
     * @param length 繩子長度
     * @return 乘積最大值
     */
    public int maxProductAfterCutting(int length) {
        if (length < 2) {
            return 0;
        }
        if (length < 4) {
            return length - 1;
        }

        int timesOf3 = length / 3;
        if (length % 3 == 1) {
            --timesOf3;
        }
        int timesOf2 = (length - timesOf3 * 3) >> 1;
        return (int) (Math.pow(3, timesOf3) * Math.pow(2, timesOf2));
    }
}

測試用例

  1. 功能測試(繩子的初始長度大於 5);
  2. 邊界值測試(繩子的初始長度分別爲 0、1、2、3、4)。

15_NumberOf1InBinary

二進制中 1 的個數

題目描述

輸入一個整數,輸出該數二進制表示中1的個數。其中負數用補碼錶示。

解法

解法一

利用整數 1,依次左移每次與 n 進行與運算,若結果不爲0,說明這一位上數字爲 1,++cnt。

此解法 i 需要左移 32 次。

不要用 n 去右移並與 1 進行與運算,因爲n 可能爲負數,右移時會陷入死循環。

public class Solution {
    public int NumberOf1(int n) {
        int cnt = 0;
        int i = 1;
        while (i != 0) {
            if ((n & i) != 0) {
                ++cnt;
            }
            i <<= 1;
        }
        return cnt;
    }
}

解法二(推薦)

  • 運算 (n - 1) & n,直至 n 爲 0。運算的次數即爲 n 的二進制中 1 的個數。

因爲 n-1 會將 n 的最右邊一位 1 改爲 0,如果右邊還有 0,則所有 0 都會變成 1。結果與 n 進行與運算,會去除掉最右邊的一個1。

舉個栗子:

若 n = 1100,
n - 1 = 1011
n & (n - 1) = 1000

即:把最右邊的 1 變成了 0。

把一個整數減去 1 之後再和原來的整數做位與運算,得到的結果相當於把整數的二進制表示中最右邊的 1 變成 0。很多二進制的問題都可以用這種思路解決。

/**
 * @author bingo
 * @since 2018/11/20
 */

public class Solution {
    /**
     * 計算整數的二進制表示裏1的個數
     * @param n 整數
     * @return 1的個數
     */
    public int NumberOf1(int n) {
        int cnt = 0;
        while (n != 0) {
            n = (n - 1 ) & n;
            ++cnt;
        }
        return cnt;
    }
}

測試用例

  1. 正數(包括邊界值 1、0x7FFFFFFF);
  2. 負數(包括邊界值 0x80000000、0xFFFFFFFF);
  3. 0。

16_Power

數值的整數次方

題目描述

給定一個 double 類型的浮點數 baseint 類型的整數 exponent。求 baseexponent 次方。

解法

注意判斷值數是否小於 0。另外 0 的 0 次方沒有意義,也需要考慮一下,看具體題目要求。

/**
 * @author bingo
 * @since 2018/11/20
 */

public class Solution {
    /**
     * 計算數值的整數次方
     * @param base 底數
     * @param exponent 指數
     * @return 數值的整數次方
     */
    public double Power(double base, int exponent) {
        double result = 1.0;
        int n = Math.abs(exponent);
        for (int i = 0; i < n; ++i) {
            result *= base;
        }

        return exponent < 0 ? 1.0 / result : result;
    }
}

測試用例

  1. 把底數和指數分別設爲正數、負數和零。

17_Print1ToMaxOfNDigits

打印從 1 到最大的 n 位數

題目描述

輸入數字 n,按順序打印出從 1 最大的 n 位十進制數。比如輸入 3,則打印出 1、2、3 一直到最大的 3 位數即 999。

解法

此題需要注意 n 位數構成的數字可能超出最大的 int 或者 long long 能表示的範圍。因此,採用字符數組來存儲數字。

關鍵是:

  • 對字符數組表示的數進行遞增操作
  • 輸出數字(0開頭的需要把0去除)
/**
 * @author bingo
 * @since 2018/11/20
 */

public class Solution {

    /**
     * 打印從1到最大的n位數
     * @param n n位
     */
    public void print1ToMaxOfNDigits(int n) {
        if (n < 1) {
            return;
        }

        char[] chars = new char[n];
        for (int i = 0; i < n; ++i) {
            chars[i] = '0';
        }

        while (!increment(chars)) {
            printNumber(chars);
        }
    }

    /**
     * 打印數字(去除前面的0)
     * @param chars 數字數組
     */
    private void printNumber(char[] chars) {
        int index = 0;
        int n = chars.length;
        for (char ch : chars) {
            if (ch != '0') {
                break;
            }
            ++index;
        }
        StringBuilder sb = new StringBuilder();
        for (int i = index; i < n; ++i) {
            sb.append(chars[i]);
        }
        System.out.println(sb.toString());
    }

    /**
     * 數字加1
     * @param chars 數字數組
     * @return 是否溢出
     */
    private boolean increment(char[] chars) {
        boolean flag = false;
        int n = chars.length;
        int carry = 1;
        for (int i = n - 1; i >= 0; --i) {

            int num = chars[i] - '0' + carry;
            if (num > 9) {
                if (i == 0) {
                    flag = true;
                    break;
                }
                chars[i] = '0';
            } else {
                ++chars[i];
                break;
            }
        }
        return flag;
    }
}

測試用例

  1. 功能測試(輸入 1、2、3…);
  2. 特殊輸入測試(輸入 -1、0)。

18_01_DeleteNodeInList

在O(1)時間內刪除鏈表節點

題目描述

給定單向鏈表的頭指針和一個節點指針,定義一個函數在 O(1) 時間內刪除該節點。

解法

判斷要刪除的節點是否是尾節點,若是,直接刪除;若不是,把要刪除節點的下一個節點賦給要刪除的節點即可。

進行n次操作,平均時間複雜度爲:( (n-1) * O(1) + O(n) ) / n = O(1),所以符合題目上說的O(1)

/**
 * @author bingo
 * @since 2018/11/20
 */

public class Solution {

    class ListNode {
        int val;
        ListNode next;
    }

    /**
     * 刪除鏈表的節點
     * @param head 鏈表頭節點
     * @param tobeDelete 要刪除的節點
     */
    public ListNode deleteNode(ListNode head, ListNode tobeDelete) {
        if (head == null || tobeDelete == null) {
            return head;
        }

        // 刪除的不是尾節點
        if (tobeDelete.next != null) {
            tobeDelete.val = tobeDelete.next.val;
            tobeDelete.next = tobeDelete.next.next;
        }
        // 鏈表中僅有一個節點
        else if (head == tobeDelete) {
            head = null;
        }
        // 刪除的是尾節點
        else {
            ListNode ptr = head;
            while (ptr.next != tobeDelete) {
                ptr = ptr.next;	
          	}
            ptr.next = null;
        }

        return head;
    }
}

測試用例

  1. 功能測試(從有多個節點的鏈表的中間/頭部/尾部刪除一個節點;從只有一個節點的鏈表中刪除唯一的節點);
  2. 特殊輸入測試(指向鏈表頭節點的爲空指針;指向要刪除節點的爲空指針)。

18_02_DeleteDuplicatedNode

刪除鏈表中重複的節點

題目描述

在一個排序的鏈表中,存在重複的結點,請刪除該鏈表中重複的結點,重複的結點不保留,返回鏈表頭指針。 例如,鏈表1->2->3->3->4->4->5 處理後爲 1->2->5

解法

解法一:遞歸

/**
 * @author bingo
 * @since 2018/11/21
 */

/*
 public class ListNode {
    int val;
    ListNode next = null;

    ListNode(int val) {
        this.val = val;
    }
}
*/
public class Solution {
    /**
     * 刪除鏈表重複的節點
     * @param pHead 鏈表頭節點
     * @return 刪除節點後的鏈表
     */
    public ListNode deleteDuplication(ListNode pHead) {
        if (pHead == null || pHead.next == null) {
            return pHead;
        }

        if (pHead.val == pHead.next.val) {
            if (pHead.next.next == null) {
                return null;
            }
            if (pHead.next.next.val == pHead.val) {
                return deleteDuplication(pHead.next);
            }
            return deleteDuplication(pHead.next.next);
        }
        pHead.next = deleteDuplication(pHead.next);
        return pHead;
    }
}

解法二

/*
 public class ListNode {
    int val;
    ListNode next = null;

    ListNode(int val) {
        this.val = val;
    }
}
*/
public class Solution {
    public ListNode deleteDuplication(ListNode pHead) {
        if (pHead == null || pHead.next == null) {
            return pHead;
        }
        
        ListNode pre = null;
        ListNode cur = pHead;
        while (cur != null) {
            if (cur.next != null && cur.next.val == cur.val) {
                int val = cur.val;
                while (cur.next != null && cur.next.val == val) {
                    cur = cur.next;
                }
                if (pre == null) {
                    pHead = cur.next;
                } else {
                    pre.next = cur.next;
                }
            } else {
                pre = cur;
            }
            cur = cur.next;
        }
        return pHead;
    }
}

測試用例

  1. 功能測試(重複的節點位於鏈表的頭部/中間/尾部;鏈表中沒有重複的節點);
  2. 特殊輸入測試(指向鏈表頭節點的爲空指針;鏈表中所有節點都是重複的)。

19_RegularExpressionsMatching

正則表達式匹配

題目描述

請實現一個函數用來匹配包括.*的正則表達式。模式中的字符.表示任意一個字符,而*表示它前面的字符可以出現任意次(包含0次)。 在本題中,匹配是指字符串的所有字符匹配整個模式。例如,字符串aaa與模式a.aab*ac*a匹配,但是與aa.aab*a均不匹配。

解法

判斷模式中第二個字符是否是 *

  • 若是,看如果模式串第一個字符與字符串第一個字符是否匹配:
      1. 若不匹配,在模式串上向右移動兩個字符j+2,相當於 a* 被忽略
      1. 若匹配,字符串後移i+1。此時模式串可以移動兩個字符j+2,也可以不移動j
  • 若不是,看當前字符與模式串的當前字符是否匹配,即 str[i] == pattern[j] || pattern[j] == ‘.’:
      1. 若匹配,則字符串與模式串都向右移動一位,i+1j+1
      1. 若不匹配,返回 fasle。
/**
 * @author bingo
 * @since 2018/11/21
 */

public class Solution {
    /**
     * 判斷字符串是否與模式串匹配
     * @param str 字符串
     * @param pattern 模式串
     * @return 是否匹配
     */
    public boolean match(char[] str, char[] pattern) {
        if (str == null || pattern == null) {
            return false;
        }
        return match(str, 0, str.length, pattern, 0, pattern.length);
    }

    private boolean match(char[] str, int i, int len1,
                          char[] pattern, int j, int len2) {
        if (i == len1 && j == len2) {
            return true;
        }

        // "",".*"
        if (i != len1 && j == len2) {
            return false;
        }

        if (j + 1 < len2 && pattern[j + 1] == '*') {
            if (i < len1 && (str[i] == pattern[j] || pattern[j] == '.')) {
                return match(str, i, len1, pattern, j + 2, len2)
                        || match(str, i + 1, len1, pattern, j, len2)
                        || match(str, i + 1, len1, pattern,j + 2, len2);
            }

            // "",".*"
            return match(str, i, len1, pattern, j + 2, len2);

        }
        if (i < len1 && (str[i] == pattern[j] || pattern[j] == '.')) {
            return match(str, i + 1, len1, pattern, j + 1, len2);
        }
        return false;

    }
}

測試用例

  1. 功能測試(模式字符串裏包含普通字符、.*;模式字符串和輸入字符串匹配/不匹配);
  2. 特殊輸入測試(輸入字符串和模式字符串是空指針、空字符串)。

20_NumericStrings

表示數值的字符串

題目描述

請實現一個函數用來判斷字符串是否表示數值(包括整數和小數)。例如,字符串"+100",“5e2”,"-123",“3.1416"和”-1E-16"都表示數值。 但是"12e",“1a3.14”,“1.2.3”,"±5"和"12e+4.3"都不是。

解法

解法一

利用正則表達式匹配即可。

[]  : 字符集合
()  : 分組
?   : 重複 0 ~ 1
+   : 重複 1 ~ n
*   : 重複 0 ~ n
.   : 任意字符
\\. : 轉義後的 .
\\d : 數字
/**
 * @author bingo
 * @since 2018/11/21
 */

public class Solution {
    /**
     * 判斷是否是數字
     * @param str
     * @return
     */
    public boolean isNumeric(char[] str) {
        return str != null 
                && str.length != 0 
                && new String(str).matches("[+-]?\\d*(\\.\\d+)?([eE][+-]?\\d+)?");
    }
}

解法二【劍指offer解法】

表示數值的字符串遵循模式A[.[B]][e|EC]或者.B[e|EC],其中A爲數值的整數部分,B緊跟小數點爲數值的小數部分,C緊跟着e或者E爲數值的指數部分。上述A和C都有可能以 + 或者 - 開頭的09的數位串,B也是09的數位串,但前面不能有正負號。

/**
 * @author mcrwayfun
 * @version v1.0
 * @date Created in 2018/12/29
 * @description
 */
public class Solution {

    private int index = 0;

    /**
     * 判斷是否是數值
     * @param  str 
     * @return 
     */
    public boolean isNumeric(char[] str) {
        if (str == null || str.length < 1) {
            return false;
        }

        // 判斷是否存在整數
        boolean flag = scanInteger(str);

        // 小數部分
        if (index < str.length && str[index] == '.') {
            index++;
            // 小數部分可以有整數或者沒有整數
            // 所以使用 ||
            flag = scanUnsignedInteger(str) || flag;
        }

        if (index < str.length && (str[index] == 'e' || str[index] == 'E')) {
            index++;
            // e或E前面必須有數字
            // e或者E後面必須有整數
            // 所以使用 &&
            flag = scanInteger(str) && flag;
        }

        return flag && index == str.length;

    }

    private boolean scanInteger(char[] str) {
        // 去除符號
        while (index < str.length && (str[index] == '+' || str[index] == '-')) {
            index++;
        }

        return scanUnsignedInteger(str);
    }

    private boolean scanUnsignedInteger(char[] str) {
        int start = index;
        while (index < str.length && str[index] >= '0' && str[index] <= '9') {
            index++;
        }
        // 判斷是否存在整數
        return index > start;
    }
}

測試用例

  1. 功能測試(正數或者負數;包含或者不包含整數部分的數值;包含或者不包含效數部分的值;包含或者不包含指數部分的值;各種不能表達有效數值的字符串);
  2. 特殊輸入測試(輸入字符串和模式字符串是空指針、空字符串)。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章