剑指Offer - Java题解 「上」

3. 数组中重复的数字

Code It Now !!!

题目描述:在一个长度为 n 的数组里,所有的数字都在 0 ~ n-1 的范围内。数组中某些数字是重复的,但不知道有几个数字重复了,也不知道每个数字重复了几次。找出数组中任意一个重复的数字。

输入:[2, 3, 1, 0, 2, 5, 3]
输出:2 或 3 

解题思路:首先可以考虑排序,先对原数组进行排序,之后遍历数组找到重复数字,时间复杂度 $O(nlogn)$,但这显然不是最优解;此外哈希表也能够解决,额外创建一个哈希表,将数组中的数字逐个放入哈希表中,只需遍历一次即可找到重复数字,时间复杂度 $O(n)$,额外空间复杂度 $O(n)$;

不过还有一种时间复杂度为 $O(n)$ 且额外空间复杂度为 $O(1)$ 的最优解法。

利用数组中的数字都在 0 ~ n-1 的范围内这一条件,如果数组中没有重复的数字,那么当数组排序后数字 i 都将会出现在下标为 i 的位置。如果有重复数字,就出现不止一个数字 i 出现在下标为 i 的位置,利用这一原理可以求解。

Java

public static int findRepeatNumber(int[] nums) {
    if (nums == null || nums.length == 0) {
        return -1;
    }
    for (int i = 0; i < nums.length; i++) {
        while (nums[i] != i && nums[nums[i]] != nums[i]) {
            swap(nums, i, nums[i]);
        }
        if (nums[i] != i) {
            return nums[i];
        }
    }
    return -1;
}

public static void swap(int[] nums, int i, int j) {
    int temp = nums[i];
    nums[i] = nums[j];
    nums[j] = temp;
}

Go

func findRepeatNumber(nums []int) int {
    for idx, val := range nums{
        if idx == val{
            continue
        }
        if nums[val] == val{
            return val
        }
        nums[val], nums[idx] = nums[idx], nums[val]
    }
    return 0
}

4. 二维数组中的查找

Code It Now!!!

题目描述:在一个 n * m 的二维数组中,每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个高效的函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。

[
  [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]
]
给定 target = 5,返回 true。
给定 target = 20,返回 false。

解题思路:从题目中可知,二维数组从左到右是递增的,从上到下也是递增的,那么如果我们从右上角作为起点,向左下方去找的话,就构成了一种二分结构,从图中可以看出,从右向左是递减的,从上到下是递增的,由此可得出三种情况:

  1. 目标值 > 当前值:接着向下找;
  2. 目标值 < 当前值:接着向左找;
  3. 目标值 = 当前值:数组中包含该整数,直接返回。

Java

public boolean findNumberIn2DArray(int[][] matrix, int target) {
    if (matrix == null || matrix.length == 0 || matrix[0].length == 0) {
        return false;
    }
    int i = 0, j = matrix[0].length - 1;
    while (i < matrix.length && j >= 0) {
        if (matrix[i][j] == target) {
            return true;
        }
        if (target > matrix[i][j]) {
            i++;
        } else {
            j--;
        }
    }
    return false;
}

Go

func findNumberIn2DArray(matrix [][]int, target int) bool {
    if len(matrix) == 0 {
        return false
    }
    rows := 0
    cols := len(matrix[0])-1
    for rows < len(matrix) && cols >=0 {
        if matrix[rows][cols] < target {
            rows++
        } else if  matrix[rows][cols]  > target{
            cols--
        } else {
            return true
        }
    }
    return false
}

5. 替换空格

Code It Now!!!

题目描述:将字符串中的空格替换成”%20”。

输入:s = "We are happy."
输出:"We%20are%20happy."

解题思路:有两种比较好的解决方法,第一种是直接开辟一个新的数组,然后从前向后遍历,依次将原字符串中的字符添加进数组中,如果遇到空格,则添加 %20 三个字符,这种方法的时间复杂度为 $O(n)$,因为开辟了新的空间,额外空间复杂度也为 $O(n)$。

另外一种,空间复杂度为 $O(1)$,就是在字符串原有的基础上进行修改,首先遍历一遍字符串中的字符,统计出空格的数量,对应的将字符串的长度向后延长,预留出 %20 增加的位置。之后,使用双指针的方式从后向前遍历,指针 p1 的初始位置为字符串扩充前最后的位置, 指针 p2 的初始位置为扩充后最后的位置,两个指针同时向前滑动,如果 p1 指向的不是空格,则将改值赋到 p2 指向的位置;如果是空格,则将 p2 指向的位置以及前面两位分别赋上 %20 三个字符。直到指针 p2 到字符串的首位字符时结束遍历。

Java

public String replaceSpace(String s) {
    StringBuilder sb = new StringBuilder(s);
    int p1 = sb.length() - 1;
    for (int i = 0; i <= p1; i++) {
        if (sb.charAt(i) == ' ') {
            sb.append("  ");
        }
    }
    int p2 = sb.length() - 1;
    while (p1 >= 0 && p2 > p1) {
        char c = sb.charAt(p1--);
        if (c == ' ') {
            sb.setCharAt(p2--, '0');
            sb.setCharAt(p2--, '2');
            sb.setCharAt(p2--, '%');
        } else {
            sb.setCharAt(p2--, c);
        }
    }
    return sb.toString();
}

Go

func replaceSpace(s string) string {
    var str string
    start := 0
    for key,value := range s {
        if value == ' '{
            str += s[start:key]
            str += "%20"
            start = key+1
        }
    }
    str += s[start:]
    return str
}

6. 从尾到头打印链表

Code It Now!!!

题目描述:输入一个链表的头节点,从尾到头反过来返回每个节点的值(用数组返回)。

输入:head = [1,3,2]
输出:[2,3,1]

解法一:递归

ArrayList<Integer> res = new ArrayList<>();
public int[] reversePrint(ListNode head) {
    helper(head);
    int[] ans = new int[res.size()];
    for (int i = 0; i < res.size(); i++) {
        ans[i] = res.get(i);
    }
    return ans;
}
public void helper(ListNode cur) {
    if (cur == null) return;
    helper(cur.next);
    res.add(cur.val);
}

解法二:栈

Stack<Integer> stack = new Stack<>();
public int[] reversePrint(ListNode head) {
    ListNode cur = head;
    while(cur != null) {
        stack.push(cur.val);
        cur = cur.next;
    }
    int[] res = new int[stack.size()];
    int index = 0;
    while(!stack.isEmpty()) {
        res[index++] = stack.pop();
    }
    return res;
}

7. 重建二叉树

Code It Now!!!

题目描述:给定二叉树的先序与中序遍历,重建二叉树。

前序遍历 preorder = [3,9,20,15,7]
中序遍历 inorder = [9,3,15,20,7]
    3
   / \
  9  20
    /  \
   15   7

解题思路:二叉树先序遍历的第一个结点是根节点,找到其在中序遍历里的位置,二分。

public TreeNode buildTree(int[] preorder, int[] inorder) {
    return helper(preorder, 0, inorder, 0, inorder.length);
}

public TreeNode helper(int[] preorder, int preIndex, int[] inorder, int inLeft, int inRight) {
    if (preIndex >= preorder.length || inLeft == inRight) {
        return null;
    }
    TreeNode root = new TreeNode(preorder[preIndex]);
    for (int i = inLeft; i < inRight; i++) {
        if (inorder[i] == preorder[preIndex]) {
            root.left = helper(preorder, preIndex + 1, inorder, inLeft, i);
            root.right = helper(preorder, preIndex + i - inLeft + 1, inorder, i + 1, inRight);
        }
    }
    return root;
}

8. 二叉树的下一个结点

Code It Now!!!

题目描述:给定一个二叉树和其中的一个结点,找出中序遍历顺序的下一个结点并且返回。注意,树中的结点不仅包含左右子结点,同时包含指向父结点的指针。

解题思路:如果右结点不为空,则为右子树的最左结点;否则,为当前树的根结点,前提是当前结点为该左子树中的结点。

public TreeLinkNode GetNext(TreeLinkNode pNode) {
    if (pNode.right != null) {
        TreeLinkNode node = pNode.right;
        while (node.left != null) {
            node = node.left;
        }
        return node;
    } else {
        while (pNode.next != null) {
            TreeLinkNode parent = pNode.next;
            if (parent.left == pNode)
                return parent;
            pNode = pNode.next;
        }
    }
    return null;
}

9. 用两个栈实现队列

Code It Now!!!

解题思路:维护两个栈,每次入队列都存到 in 栈中,出队列从 out 栈出。

Stack<Integer> in = new Stack<Integer>();
Stack<Integer> out = new Stack<Integer>();

public void push(int node) {
    in.push(node);
}

public int pop() throws Exception {
    if (out.isEmpty())
        while (!in.isEmpty())
            out.push(in.pop());

    if (out.isEmpty())
        throw new Exception("queue is empty");

    return out.pop();
}

10.1 斐波那契数列

Code It Now!!!

F(0) = 0,   F(1) = 1
F(N) = F(N - 1) + F(N - 2), 其中 N > 1.
public int fib(int n) {
    if (n < 2) return n;
    int a = 0, b = 1;
    for (int i = 2; i <= n; i++) {
        int sum = a + b;
        a = b;
        b = sum;
    }
    return b;
}

10.2 矩形覆盖

Code It Now!!!

题目描述:我们可以用 2*1 的小矩形横着或者竖着去覆盖更大的矩形。请问用 n 个 2*1 的小矩形无重叠地覆盖一个 2*n 的大矩形,总共有多少种方法?

解题思路:归纳法能看出符合斐波那契数列。

public int RectCover(int target) {
    if (target <= 2) return target;
    int a = 1, b = 2;
    for (int i = 3; i <= target; i++) {
        int sum = a + b;
        a = b;
        b = sum;
    }
    return b;
}

10.3 跳台阶

Code It Now!!!

题目描述:一只青蛙一次可以跳上 1 级台阶,也可以跳上 2 级。求该青蛙跳上一个 n 级的台阶总共有多少种跳法。

解题思路:符合斐波那契数列。

public int numWays(int n) {
    if (n == 0) return 1;
    if (n <= 2) return n;
    int a = 1, b = 2;
    for (int i = 3; i <= n; i++) {
        int sum = a + b;
        a = b;
        b = sum;
    }
    return b;
}

10.4 变态跳台阶

Code It Now!!!

题目描述:一只青蛙一次可以跳上 1 级台阶,也可以跳上 2 级……它也可以跳上 n 级。求该青蛙跳上一个 n 级的台阶总共有多少种跳法。

解法一:每个台阶可以看做一块木板,让青蛙跳上去,n 个台阶就有 n 块木板,最后一块木板是青蛙达到的目的地,必须存在,其他 n-1 块木板可以任意选择是否存在,即每个木板有存在和不存在两种选择,n-1 块木板就有 $2^{n-1}$ 种跳法。

public int jumpFloor(int target) {
    if (target <= 0) return 0;
		return (int) Math.pow(2, target -1);
}

解法二:动态规划,上一题普通跳台阶种,跳到台阶 n 的跳法为跳到 n-1n-2 两个台阶跳法之和,而本题没有限制青蛙最多能向上跳几阶台阶,所以跳到台阶 n 的跳法为前面所有跳法之和。

public int JumpFloor(int target) {
    int[] dp = new int[target];
    Arrays.fill(dp, 1);
    for (int i = 1; i < target; i++) {
        for (int j = 0; j < i; j++) {
            dp[i] += dp[j];
        }
    }
    return dp[target - 1];
}

11. 旋转数组的最小数字

Code It Now!!!

题目描述:把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。输入一个非递减排序的数组的一个旋转,输出旋转数组的最小元素。

输入:[3,4,5,1,2]
输出:1

解题思路:二分查找,如果找数组中的最小数字,只需要遍历一下数组就可以了,但是这道题我们要利用上旋转数组这一条件。数组在没有旋转之前是一个递增数组,也就是下标小的元素一定小于或等于下标大的元素,那数组旋转之后就打破了这一特性,可以利用二分查找寻找哪一边不符合左边的元素小于右边的元素,那最小数字一定在那一边,如果不确定在哪一边,就将有临界向左挪一位。

public int minArray(int[] numbers) {
    int left = 0, right = numbers.length - 1;
    while (left < right) {
        int mid = left + (right - left) / 2;
        if (numbers[mid] > numbers[right]) {
            left = mid + 1;
        } else if (numbers[mid] < numbers[right]) {
            right = mid;
        } else {
            right--;
        }
    }
    return numbers[left];
}

12. 矩阵中的路径

Code It Now!!!

题目描述:判断在一个矩阵中是否存在一条包含某字符串所有字符的路径。路径可以从矩阵中的任意一个格子开始,每一步可以在矩阵中向上下左右移动一个格子。如果一条路径经过了矩阵中的某一个格子,则该路径不能再进入该格子。

解题思路:深度优先搜索。

int[][] directions = { {0, 1}, {0, -1}, {1, 0}, {-1, 0} };
public boolean exist(char[][] board, String word) {
    for (int i = 0; i < board.length; i++) {
        for (int j = 0; j < board[0].length; j++) {
            boolean flag = backtrack(board, word, 0, i, j, new boolean[board.length][board[0].length]);
            if (flag) return true;
        }
    }
    return false;
}

public boolean backtrack(char[][] board, String word, int index, int r, int c, boolean[][] marked) {

    if (index == word.length()) {
        return true;
    }

    if (r < 0 || c < 0 || r >= board.length || c >= board[0].length || marked[r][c] || word.charAt(index) != board[r][c]) {
        return false;
    }

    marked[r][c] = true;    
    for (int[] direction: directions) {
        boolean flag = backtrack(board, word, index + 1, r + direction[0], c + direction[1], marked);
        if (flag) return true;
    }
    marked[r][c] = false;
    return false;
}

13. 机器人的运动范围

Code It Now!!!

题目描述:地上有一个 m 行和 n 列的方格。一个机器人从坐标 [0,0] 的格子开始移动,每一次只能向左,右,上,下四个方向移动一格,但是不能进入行坐标和列坐标的数位之和大于 k 的格子。 例如,当 k 为 18 时,机器人能够进入方格 [35,37],因为 3+5+3+7=18。但是,它不能进入方格 [35,38],因为 3+5+3+8=19。请问该机器人能够达到多少个格子?

解题思路:深度优先搜索。

int[][] next = { {1, 0}, {-1, 0}, {0, 1}, {0, -1} };
int[][] digitSum;
int count = 0;
int rows, cols, threshold;
public int movingCount(int m, int n, int k) {
    this.rows = m;
    this.cols = n;
    this.threshold = k;
    initStatus(rows, cols);
    boolean[][] marked = new boolean[rows][cols];
    dfs(marked, 0, 0);
    return count;
}

private void dfs(boolean[][] marked, int r, int c) {
    if (r < 0 || r >= rows || c < 0 || c >= cols || marked[r][c]) return;

    marked[r][c] = true;
    if (digitSum[r][c] > threshold) return;
    count++;
    for (int[] n : next) {
        dfs(marked, r + n[0], c + n[1]);
    }
}

private void initStatus(int rows, int cols) {
    int n = Math.max(rows, cols);
    int[] digitSum = new int[n];
    for (int i = 0; i < n; i++) {
        int tmp = i;
        while (tmp != 0) {
            digitSum[i] += tmp % 10;
            tmp /= 10;
        }
    }

    this.digitSum = new int[rows][cols];
    for (int i = 0; i < rows; i++) {
        for (int j = 0; j < cols; j++) {
            this.digitSum[i][j] = digitSum[i] + digitSum[j];
        }
    }
}

14.1 剪绳子 I

Code It Now!!!

题目描述:把一根绳子剪成多段,并且使得每段的长度乘积最大。

输入: 10
输出: 36
解释: 10 = 3 + 3 + 4, 3 × 3 × 4 = 36

解题思路:动态规划

  • 边界条件:dp[1] = 1,表示长度为 2 的绳子最大乘积为 1
  • 状态转移方程:
public int cuttingRope(int n) {
    int[] dp = new int[n + 1];
    dp[1] = 1;
    for (int i = 2; i <= n; i++)
        for (int j = 1; j < i; j++)
            dp[i] = Math.max(dp[i], Math.max(j * (i - j), dp[j] * (i - j)));
    return dp[n];
}

14.2 剪绳子 II

Code It Now!!!

题目描述:在上一题的基础上扩大了范围,答案需要取模 1e9+7 (1000000007)

解题思路:本题如果使用 DP 难度就增大了,因为 long 已经不足以去存储中间结果的状态,因此只能使用 BigInteger 的方式去做。而此题更适合使用贪心算法去解决。

通过数学归纳可知,只有长度为 2 和 3 的绳子不应再切分,且 3 比 2 更优。

public int cuttingRope(int n) {
    if(n == 2) return 1;
    if(n == 3) return 2;
    int mod = (int)1e9 + 7;
    long res = 1;
    while(n > 4) {
        res *= 3;
        res %= mod;
        n -= 3;
    }
    return (int)(res * n % mod);
}

15. 二进制中 1 的个数

Code It Now!!!

题目描述:输入一个整数,输出该数二进制表示中 1 的个数。其中负数用补码表示。

输入:00000000000000000000000000001011
输出:3

解题思路:利用 n & (n - 1) 操作的特性,将最右边的 1 变为 0

  • (n−1) : 二进制数字 n 最右边的 1 变成 0 ,此 1 右边的 0 都变成 1
  • n & (n - 1) : 二进制数字 n 最右边的 1 变成 0 ,其余不变。
public int hammingWeight(int n) {
    int count = 0;
    while (n != 0) {
        n = n & (n - 1);
        count++;
    }
    return count;
}

16. 数值的整数次方

Code It Now!!!

题目描述:给定一个 double 类型的浮点数 base 和 int 类型的整数 exponent,求 base 的 exponent 次方。

输入: 2.00000, 10
输出: 1024.00000

解题思路:经典位运算快速幂算法。

public double myPow(double x, int n) {
    if (x == 0) return 0;
    if (n < 0) {
        x = 1 / x;
        n = -n;
    }
    double res = 1;
    while (n != 0) {
        if ((n & 1) == 1) {
            res *= x;
        }
        x *= x;
        n /= 2;
    }
    return res;
}

17. 打印从 1 到最大的 n 位数

Code It Now!!!

题目描述:输入数字 n,按顺序打印出从 1 到最大的 n 位十进制数。比如输入 3,则打印出 1、2、3 一直到最大的 3 位数即 999

解题思路:题目没有说明数据位数范围,应按照大数问题(超出编程语言自带的数据结构表示范围)处理。

public void print1ToMaxOfNDigits(int n) {
    if (n <= 0)
        return;
    char[] number = new char[n];
    print1ToMaxOfNDigits(number, 0);
}

private void print1ToMaxOfNDigits(char[] number, int digit) {
    if (digit == number.length) {
        printNumber(number);
        return;
    }
    for (int i = 0; i < 10; i++) {
        number[digit] = (char) (i + '0');
        print1ToMaxOfNDigits(number, digit + 1);
    }
}

private void printNumber(char[] number) {
    int index = 0;
    while (index < number.length && number[index] == '0')
        index++;
    while (index < number.length)
        System.out.print(number[index++]);
    System.out.println();
}

18.1 在 O(1) 时间内删除链表节点

Code It Now!!!

题目描述:给定单向链表的头指针和一个要删除的节点的值,定义一个函数删除该节点。

输入: head = [4,5,1,9], val = 5
输出: [4,1,9]

解题思路:如果该节点不是尾节点,那么可以直接将下一个节点的值赋给该节点,然后令该节点指向下下个节点,再删除下一个节点,时间复杂度为 $O(1)$;否则,就需要先遍历链表,找到节点的前一个节点,然后让前一个节点指向 null,时间复杂度为 $O(N)$。

public ListNode deleteNode(ListNode head, ListNode tobeDelete) {
    if (head == null || tobeDelete == null)
        return null;
    if (tobeDelete.next != null) {
        // 要删除的节点不是尾节点
        ListNode next = tobeDelete.next;
        tobeDelete.val = next.val;
        tobeDelete.next = next.next;
    } else {
        if (head == tobeDelete)
            // 只有一个节点
            head = null;
        else {
            ListNode cur = head;
            while (cur.next != tobeDelete)
                cur = cur.next;
            cur.next = null;
        }
    }
    return head;
}

18.2 删除链表中重复的结点

Code It Now!!!

题目描述:在一个排序的链表中,存在重复的结点,请删除该链表中重复的结点,重复的结点不保留,返回链表头指针。

解题思路:递归的思想,每次删除一个重复的节点。

public ListNode deleteDuplication(ListNode pHead) {
    if (pHead == null || pHead.next == null)
        return pHead;
    ListNode next = pHead.next;
    if (pHead.val == next.val) {
        while (next != null && pHead.val == next.val)
            next = next.next;
        return deleteDuplication(next);
    } else {
        pHead.next = deleteDuplication(pHead.next);
        return pHead;
    }
}

19. 正则表达式匹配

Code It Now!!!

题目描述:请实现一个函数用来匹配包括 .* 的正则表达式。模式中的字符 . 表示任意一个字符,而 * 表示它前面的字符可以出现任意次(包含 0 次)。

输入:
s = "aa"
p = "a*"
输出: true

解题思路:动态规划

  • 定义状态 dp[i][j]s 的前 i 个字符能够匹配 p 的前 j 个字符,注意这里的范围是 (] 的,即 (0, i](0, j]

  • 转移方程:

    • 若主串字符与正则串字符相等,或者正则串的字符为 .,则 $dp[i][j] = dp[i - 1][j - 1]$
    • 若正则串字符为 *,可能有两种情况:
      • 正则串表示 0 个字符,直接砍掉正则串的后面两个, $dp[i][j] = dp[i][j-2]$
      • 正则串表示当前字符出现多次,正则串不动,主串向前移动一位,$dp[i][j] = dp[i-1][j]$
public boolean isMatch(String s, String p) {
    int m = s.length, n = p.length;
    boolean[][] dp = new boolean[m + 1][n + 1];
    
    // 空主串与空模式串匹配
    dp[0][0] = true;
    // 空主串与非空模式串
    for (int i = 1; i <= n; i++) {
        if (p.charAt(i - ) == '*') {
            dp[0][i] = dp[0][i - 2];
        }
    }
    
    for (int i = 1; i <= m; i++) {
        for (int j = 1; j <= n; j++) {
            // 字符相等
            if (s.charAt(i - 1) == p.charAt(j - 1) || p.charAt(j - 1) == '.') {
                dp[i][j] = dp[i - 1][j - 1];
            } else if (p.charAt(j - 1) == '*') {
                if (p.charAt(j - 2) == s.charAt(i - 1) || p.charAt(j - 2) == '.') {
                    dp[i][j] |= dp[i - 1][j]; // a* 表示出现多次
                    dp[i][j] |= dp[i][j - 2]; // a* 表示出现 0 次
                } else {
                    dp[i][j] = dp[i][j - 2];  // a* 表示出现 0 次
                }
            }
        }
    }
    return dp[m][n];
}

20. 表示数值的字符串

Code It Now!!!

题目描述:实现一个函数用来判断字符串是否表示数值(包括整数和小数)。例如,字符串 +1005e2-1233.1416-1E-16 都表示数值。 但是 12e1a3.141.2.3+-512e+4.3 都不是。

解法一:正则表达式

public boolean isNumber(String s) {
    if (s == null || s.length() == 0) return false;
    return s.trim().matches("[+-]?([0-9]+(\\.[0-9]*)?|\\.[0-9]+)(e[+-]?[0-9]+)?");
}

解法二:条件判断

public boolean isNumber(String s) {
    if(s == null || s.length() == 0) return false;
    // 标记是否遇到相应情况
    boolean numSeen = false;
    boolean dotSeen = false;
    boolean eSeen = false;
    char[] str = s.trim().toCharArray();
    for(int i = 0;i < str.length; i++) {
        if(str[i] >= '0' && str[i] <= '9'){
            numSeen = true;
        } else if(str[i] == '.'){
            // .之前不能出现.或者e
            if(dotSeen || eSeen){
                return false;
            }
            dotSeen = true;
        } else if(str[i] == 'e' || str[i] == 'E') {
            // e之前不能出现e,必须出现数
            if(eSeen || !numSeen){
                return false;
            }
            eSeen = true;
            numSeen = false;// 重置numSeen,排除123e或者123e+的情况,确保e之后也出现数
        } else if(str[i] == '-' || str[i] == '+') {
            // +-出现在0位置或者e/E的后面第一个位置才是合法的
            if(i != 0 && str[i-1] != 'e' && str[i-1] != 'E'){
                return false;
            }
        } else{// 其他不合法字符
            return false;
        }
    }
    return numSeen;
}

21. 调整数组顺序使奇数位于偶数前面

Code It Now!!!

题目描述:需要保证奇数和奇数、偶数和偶数之间的相对位置不变。

解题思路:Leetcode 上的题目没有要求相对顺序不变,可以用双指针解决,双指针的基本操作。

public int[] exchange(int[] nums) {
    int oddCount = 0;
    for (int x : nums) {
        if (x % 2 == 1) oddCount++;
    }
    int[] copy = nums.clone();
    int i = 0, j = oddCount;
    for (int x : copy) {
        if (x % 2 == 1) nums[i++] = x;
        else nums[j++] = x;
    }
    return nums; 
}

22. 链表中倒数第 K 个节点

Code It Now!!!

题目描述:输入一个链表,输出该链表中倒数第 k 个节点。

给定一个链表: 1->2->3->4->5, 和 k = 2.
返回链表 4->5.

解题思路:快慢指针,p1 指针先走到正数第 k 个节点,之后和 p2 一同向前移动,直到 p1 到达尾节点的后面,此时 p2 指向的正是到处第 k 个节点。

public ListNode getKthFromEnd(ListNode head, int k) {
    ListNode p1 = head, p2 = head;
    while (k-- > 0) {
        p1 = p1.next;
    }
    while (p1 != null) {
        p1 = p1.next;
        p2 = p2.next;
    }
    return p2;
}

23. 链表中环的入口结点

Code It Now!!!

题目描述:给一个链表,若其中包含环,请找出该链表的环的入口结点。

解题思路:假设链表头是 X,环的第一个节点是 Yslowfast 第一次的交点是 Z。各段的长度分别是 a,b,cslow 每次向前走一个节点,fast 每次向前走两个结点,第一次相遇时 slow 走过的距离为 a+bfast 走过的距离为 a+b+c+b,因为 fast 的速度是 slow 的两倍,所以 fast 走的距离是 slow 的两倍,由 2(a+b) = a+b+c+b 可得 a = c。这时,让两个指针分别从 XZ 开始走,每次都走一步,那么正好会在 Y 相遇,也就是环的第一个节点。

public ListNode EntryNodeOfLoop(ListNode pHead) {
    if (pHead == null || pHead.next == null)
        return null;
    ListNode slow = pHead, fast = pHead;
    do {
        fast = fast.next.next;
        slow = slow.next;
    } while (slow != fast);
    fast = pHead;
    while (slow != fast) {
        slow = slow.next;
        fast = fast.next;
    }
    return slow;
}

24. 反转链表

Code It Now!!!

题目描述:定义一个函数,输入一个链表的头节点,反转该链表并输出反转后链表的头节点。

方法一:递归

public ListNode reverseList(ListNode head) {
    if (head == null || head.next == null) return head;
    ListNode next = head.next;
    head.next = null;
    ListNode newHead = reverseList(next);
    next.next = head;
    return newHead;
}

方法二:头插法

public ListNode reverseList(ListNode head) {
    ListNode newList = new ListNode(-1);
    while (head != null) {
        ListNode next = head.next;
        head.next = newList.next;
        newList.next = head;
        head = next;
    }
    return newList.next;
}

25. 合并两个排序的链表

Code It Now!!!

题目描述:输入两个递增排序的链表,合并这两个链表并使新链表中的节点仍然是递增排序的。

public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
    ListNode dummyNode = new ListNode(-1);
    ListNode cur = dummyNode;
    while (l1 != null && l2 != null) {
        if (l1.val > l2.val) {
            cur.next = l2;
            l2 = l2.next;
        } else {
            cur.next = l1;
            l1 = l1.next;
        }
        cur = cur.next;
    }
    cur.next = l1 != null ? l1 : l2;
    return dummyNode.next;
}