【剑指offer】51-67题

51.在一个长度为n的数组里的所有数字都在0到n-1的范围内,找出数组中任意一个重复的数字

思路:若下标大于length,则减去length,最后再加上length,若下标的数组值大于length,则返回true。或使用辅助空间(HashSet)

代码实现

public boolean duplicate(int numbers[],int length,int [] duplication) {
    if (numbers == null || length == 0 || length == 1)
        return false;
    for (int i = 0; i < length; i++) {
        int index = numbers[i];
        if (index >= length)
            index -= length;
        if (numbers[index] >= length) {
            duplication[0] = index;
            return true;
        }
        numbers[index] += length;
    }
    return false;
}

52.给定一个数组A[0,1,…,n-1],请构建一个数组B[0,1,…,n-1],其中B中的元素B[i]=A[0]A[1]A[i-1]*A[i+1]…*A[n-1]。其中A[i] = 1。不能使用除法,

思路:使用矩阵法求解,将矩阵分为上三角矩阵和下三角矩阵,分别求乘积

代码实现

public int[] multiply(int[] A) {
    int length = A.length;
    int[] B = new int[length];
    if(length != 0 ){
        B[0] = 1;
        //计算下三角连乘
        for(int i = 1; i < length; i++){
            B[i] = B[i-1] * A[i-1];
        }
        int temp = 1;
        //计算上三角连乘
        for(int j = length-2; j >= 0; j--){
            temp *= A[j+1];
            B[j] *= temp;
        }
    }
    return B;
}

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

思路:当字符串只有一个字符时,进行判断,否则就有两种递归情况,(1)当模式中的第二个字符不是“”时:如果字符串第一个字符和模式中的第一个字符相匹配或是点那么字符串和模式都后移一个字符,然后匹配剩余的;如果 字符串第一个字符和模式中的第一个字符相不匹配,直接返回false。(2)当模式中的第二个字符是“”时:如果字符串第一个字符跟模式第一个字符不匹配,则模式后移2个字符,继续匹配;如果字符串第一个字符跟模式第一个字符匹配或是点,可以有3种匹配方式:1 >模式后移2字符,相当于x*被忽略;2>字符串后移1字符,模式后移2字符;3>字符串后移1字符,模式不变,即继续匹配字符下一位,因为 * 可以匹配多位;

代码实现

public boolean match(char[] str, char[] pattern) {
    if (str == null || pattern == null)
        return false;
    // 若字符串的长度为1
    if (str.length == 1) {
        if (pattern.length == 1){
            if (str[0] == pattern[0] || pattern[0] == '.')
                return true;
            return false;
        }
    }
    int sindex = 0;
    int pindex = 0;
    return matchIndex(str,sindex,pattern,pindex);
}

public boolean matchIndex(char[] str,int sindex, char[] pattern, int pindex) {
    // str和pattern同时到达末尾,则匹配成功
    if (sindex == str.length && pindex == pattern.length)
        return true;
    // 若pattern先到尾,而str没有到达末尾,则匹配失败
    if (sindex != str.length && pindex == pattern.length)
        return false;
    // 若pattern第二个字符是*
    if (pindex + 1 < pattern.length && pattern[pindex + 1] == '*') {
        if (sindex != str.length && pattern[pindex] == str[sindex] ||
                sindex != str.length && pattern[pindex] == '.') {
            return matchIndex(str,sindex+1,pattern,pindex+2)
                    || matchIndex(str,sindex,pattern,pindex+2)
                    || matchIndex(str,sindex+1,pattern,pindex);
        } else {
            return matchIndex(str,sindex,pattern,pindex+2);
        }
    }
    // 若pattern第二个字符不是*
    if (sindex != str.length && pattern[pindex] == str[sindex] ||
            sindex != str.length && pattern[pindex] == '.')
        return matchIndex(str,sindex+1,pattern,pindex+1);
    return false;
}

54.请实现一个函数用来判断字符串是否表示数值(包括整数和小数)

思路:逐个字符进行判断,e或E和小数点最多出现一次,而e或E的前一个必须是数字,且不能是第一个或最后一个字符,符号的前一个字符不能是e或E。也可用正则表达式判断!

代码实现

public boolean isNumeric(char[] str) {
    if (str == null)
        return false;
    int index = 0;
    int ecount = 0;
    int point = 0;
    // 如果第一个字符是符号就跳过
    if (str[0] == '-' || str[0] == '+')
        index++;

    for (int i = index; i < str.length; i++) {
        if (str[i] == '-' || str[i] == '+') {
            if (str[i-1] != 'e' && str[i-1] != 'E')
                return false;
            continue;
        }

        if (str[i] == 'e' || str[i] == 'E') {
            ecount++;
            if (ecount > 1)
                return false;
            if (i == 0 || str[i-1] < 48 || str[i-1] > 57 || i == str.length-1)
                return false;
            point++;
            continue;
        }

        if (str[i] == '.') {
            point++;
            if (point > 1)
                return false;
            continue;
        }
        // 出现非数字且不是e/E则返回false(小数点和符号用continue跳过了)
        if ((str[i] < 48 || str[i] > 57) && (str[i] != 'e') && (str[i] != 'E'))
            return false;
    }
    return true;
}

55.请实现一个函数用来找出字符流中第一个只出现一次的字符。

思路:借助辅助空间进行判断,如字符数组。

代码实现

char[] chars = new char[256];
StringBuilder sb = new StringBuilder();

public void Insert(char ch) {
    sb.append(ch);
    chars[ch]++;
}

public char FirstAppearingOnce() {
    char[] str = sb.toString().toCharArray();
    for (char c : str) {
        if (chars[c] == 1) {
            return c;
        }
    }
    return '#';
}

56.一个链表中包含环,请找出该链表的环的入口结点。

思路:定义快慢两个指针,相遇后(环中相汇点)将快指针指向pHead 然后一起走,每次往后挪一位,相遇的节点即为所求。详细分析:相遇即p1==p2时,p2所经过节点数为2x,p1所经过节点数为x,设环中有n个节点,p2比p1多走一圈有2x=n+x; n=x;可以看出p1实际走了一个环的步数,再让p2指向链表头部,p1位置不变,p1,p2每次走一步直到p1==p2; 此时p1指向环的入口。

代码实现

public ListNode EntryNodeOfLoop(ListNode pHead) {
    if (pHead == null || pHead.next == null)
        return null;
    ListNode slow = pHead;
    ListNode fast = pHead;

    while (fast != null && fast.next != null) {
        slow = slow.next;
        fast = fast.next.next;
        if (slow == fast){
            fast = pHead;
            while (fast != slow) {
                fast = fast.next;
                slow = slow.next;
            }
            if (fast == slow)
                return slow;
        }
    }
    return null;
}

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

思路:先新建一个头节点,然后向后查找值相同的节点,重复查找后删除

代码实现

public ListNode deleteDuplication(ListNode pHead) {
    if (pHead == null)
        return null;
    // 新建一个节点,防止头结点被删除
    ListNode first = new ListNode(-1);
    first.next = pHead;
    ListNode p = pHead;
    // 指向前一个节点
    ListNode preNode = first;

    while (p != null && p.next != null) {
        if (p.val == p.next.val) {
            int val = p.val;
            // 向后重复查找
            while (p != null && p.val == val) {
                p = p.next;
            }
            // 上个非重复值指向下一个非重复值:即删除重复值
            preNode.next = p;
        }else {
            // 如果当前节点和下一个节点值不等,则向后移动一位
            preNode = p;
            p = p.next;
        }
    }
    return first.next;
}

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

思路:若节点右孩子存在,则设置一个指针从该节点的右孩子出发,一直沿着指向左子结点的指针找到的叶子节点即为下一个节点;若节点不是根节点。如果该节点是其父节点的左孩子,则返回父节点;否则继续向上遍历其父节点的父节点,重复之前的判断,返回结果

代码实现

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

    while (pNode.next != null) {
        // 找第一个当前节点是父节点左孩子的节点
        if (pNode.next.left == pNode)
            return pNode.next;
        pNode = pNode.next;
    }
    return null;
}

59.请实现一个函数,用来判断一颗二叉树是不是对称的。注意,如果一个二叉树同此二叉树的镜像是同样的,定义其为对称的。

思路:利用递归进行判断,若左子树的左孩子等于右子树的右孩子且左子树的右孩子等于右子树的左孩子,并且左右子树节点的值相等,则是对称的。

代码实现

public boolean isSymmetrical(TreeNode pRoot){
    if (pRoot == null)
        return true;
    return isCommon(pRoot.left, pRoot.right);
}

public boolean isCommon(TreeNode leftNode, TreeNode rightNode) {
    if (leftNode == null && rightNode == null)
        return true;

    if (leftNode != null && rightNode != null)
        return leftNode.val == rightNode.val &&
                isCommon(leftNode.left,rightNode.right) &&
                isCommon(leftNode.right,rightNode.left);
    return false;
}

60.请实现一个函数按照之字形打印二叉树,即第一行按照从左到右的顺序打印,第二层按照从右至左的顺序打印,第三行按照从左到右的顺序打印,依此类推。

思路:利用两个栈的辅助空间分别存储奇数偶数层的节点,然后打印输出。或使用链表的辅助空间来实现,利用链表的反向迭实现逆序输出。

代码实现

public ArrayList<ArrayList<Integer>> Print(TreeNode pRoot) {
    ArrayList<ArrayList<Integer>> res = new ArrayList<>();
    if (pRoot == null)
        return res;
    Stack<TreeNode> s1 = new Stack<>();  // s1表示奇数,从右向左输出
    Stack<TreeNode> s2 = new Stack<>();  // s2表示偶数,从左向右输出
    s1.push(pRoot);
    int level = 1;
    while (!s1.empty() || !s2.empty()) {
        if (level % 2 != 0) {
            ArrayList<Integer> list = new ArrayList<>();
            while (!s1.empty()) {
                TreeNode node = s1.pop();
                if (node != null) {
                    list.add(node.val);
                    s2.push(node.left);
                    s2.push(node.right);
                }
            }

            if (!list.isEmpty()) {
                res.add(list);
                level++;
            }
        } else {
            ArrayList<Integer> list = new ArrayList<>();
            while (!s2.empty()) {
                TreeNode node = s2.pop();
                if (node != null) {
                    list.add(node.val);
                    s1.push(node.right);
                    s1.push(node.left);
                }
            }

            if (!list.isEmpty()) {
                res.add(list);
                level++;
            }
        }
    }
    return res;
}

61.从上到下按层打印二叉树,同一层结点从左至右输出。每一层输出一行。

思路:利用辅助空间链表或队列来存储节点,每层输出。

代码实现

public ArrayList<ArrayList<Integer>> Print(TreeNode pRoot) {
    ArrayList<ArrayList<Integer>> res = new ArrayList<>();
    if (pRoot == null)
        return res;
    LinkedList<TreeNode> queue = new LinkedList<>();
    queue.add(pRoot);
    ArrayList<Integer> list = new ArrayList<>();
    int start = 0;
    int end = 1;

    while (!queue.isEmpty()) {
        TreeNode node = queue.pop();
        list.add(node.val);
        start++;
        if (node.left != null)
            queue.offer(node.left);
        if (node.right != null)
            queue.offer(node.right);

        if (start == end) {
            start = 0;
            end = queue.size();
            res.add(new ArrayList<>(list));
            list.clear();
        }
    }
    return res;
}

62.请实现两个函数,分别用来序列化和反序列化二叉树

思路:序列化:前序遍历二叉树存入字符串中;反序列化:根据前序遍历重建二叉树。

代码实现

public String Serialize(TreeNode root) {
    StringBuffer sb = new StringBuffer();
    if (root == null){
        sb.append("#,");
        return sb.toString();
    }

    sb.append(root.val + ",");
    sb.append(Serialize(root.left));
    sb.append(Serialize(root.right));
    return sb.toString();
}

public int index = -1;
public TreeNode Deserialize(String str) {
    index++;
    int len = str.length();
    String[] strr = str.split(",");
    TreeNode node = null;

    if (index >= len)
        return null;

    if (!strr[index].equals("#")){
        node = new TreeNode(Integer.valueOf(strr[index]));
        node.left = Deserialize(str);
        node.right = Deserialize(str);
    }
    return node;
}

63.给定一颗二叉搜索树,请找出其中的第k大的结点

思路:二叉搜索树按照中序遍历的顺序打印出来正好就是排序好的顺序,第k个结点就是第K大的节点,分别递归查找左右子树的第K个节点,或使用非递归借用栈的方式查找,当count=k时返回根节点。

代码实现

int count = 0;
public TreeNode KthNode(TreeNode pRoot, int k) {
    if (pRoot == null || k < 1)
        return null;
    count++;
    if (count == k) {
        return pRoot;
    }

    TreeNode leftNode = KthNode(pRoot.left,k);
    if (leftNode != null)
        return leftNode;

    TreeNode rightNode = KthNode(pRoot.right,k);
    if (rightNode != null)
        return rightNode;
    return null;
}

64.如何得到一个数据流中的中位数?如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值。如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值。

思路:创建优先级队列维护大顶堆和小顶堆两个堆,并且小顶堆的值都大于大顶堆的值,2个堆个数的差值小于等于1,所以当插入个数为奇数时:大顶堆个数就比小顶堆多1,中位数就是大顶堆堆头;当插入个数为偶数时,使大顶堆个数跟小顶堆个数一样,中位数就是 2个堆堆头平均数。也可使用集合类的排序方法。

代码实现

int count = 0;
PriorityQueue<Integer> minHeap = new PriorityQueue<>();
PriorityQueue<Integer> maxHeap = new PriorityQueue<>(16, new Comparator<Integer>() {
    @Override
    public int compare(Integer o1, Integer o2) {
        return o2.compareTo(o1);
    }
});

public void Insert(Integer num) {
    count++;
    // 当数据的个数为奇数时,进入大根堆
    if ((count & 1) == 1) {
        minHeap.offer(num);
        maxHeap.offer(minHeap.poll());
    } else {
        maxHeap.offer(num);
        minHeap.offer(maxHeap.poll());
    }
}

public Double GetMedian() {
    if (count == 0)
        return null;

    // 当数据个数是奇数时,中位数就是大根堆的顶点
    if ((count & 1) == 1) {
        return Double.valueOf(maxHeap.peek());
    } else {
        return Double.valueOf((minHeap.peek() + maxHeap.peek())) / 2;
    }
}

65.给定一个数组和滑动窗口的大小,找出所有滑动窗口里数值的最大值

思路:两个for循环,第一个for循环滑动窗口,第二个for循环滑动窗口中的值,寻找最大值。还可以使用时间复杂度更低的双端队列求解。

代码实现

public ArrayList<Integer> maxInWindows(int [] num, int size) {
    ArrayList<Integer> list = new ArrayList<>();
    if (num == null || size < 1 || num.length < size)
        return list;
    int length = num.length - size + 1;

    for (int i = 0; i < length; i++) {
        int current = size + i;
        int max = num[i];
        for (int j = i; j < current; j++) {
            if (max < num[j]) {
                max = num[j];
            }
        }
        list.add(max);
    }
    return list;
}

66.请设计一个函数,用来判断在一个矩阵中是否存在一条包含某字符串所有字符的路径。路径可以从矩阵中的任意一个格子开始,每一步可以在矩阵中向左,向右,向上,向下移动一个格子。如果一条路径经过了矩阵中的某一个格子,则该路径不能再进入该格子。

思路:回溯法,双层for循环,判断每一个点,每次递归调用上下左右四个点,用flag标志是否已经匹配(return),进行判断点的位置是否越界,是否已经正确匹配,判断矩阵的路径与模式串的第index个字符是否匹配。

代码实现

public boolean hasPath(char[] matrix, int rows, int cols, char[] str) {
    int flag[] = new int[matrix.length];
    for (int i = 0; i < rows; i++) {
        for (int j = 0; j < cols; j++) {
            if (helper(matrix, rows, cols, i, j, str, 0, flag))
                return true;
        }
    }
    return false;
}

private boolean helper(char[] matrix,int rows,int cols,int i,int j,char[] str,int k,int[] flag) {
    int index = i * cols + j;
    if (i < 0 || i >= rows || j < 0 || j >= cols || matrix[index] != str[k] || flag[index] == 1)
        return false;

    if(k == str.length - 1)
        return true;
    flag[index] = 1;

    if (helper(matrix, rows, cols, i - 1, j, str, k + 1, flag)
            || helper(matrix, rows, cols, i + 1, j, str, k + 1, flag)
            || helper(matrix, rows, cols, i, j - 1, str, k + 1, flag)
            || helper(matrix, rows, cols, i, j + 1, str, k + 1, flag)) {
        return true;
    }
    flag[index] = 0;
    return false;
}

67.地上有一个m行和n列的方格。一个机器人从坐标0,0的格子开始移动,每一次只能向左,右,上,下四个方向移动一格,但是不能进入行坐标和列坐标的数位之和大于k的格子。

思路:利用递归实现,每次只能走上下左右四个点,进行判断点的位置是否越界,点数之和是否大于K,是否已经走过了。

代码实现

public int movingCount(int threshold, int rows, int cols) {
    int flag[][] = new int[rows][cols]; //记录是否已经走过
    return helper(0, 0, rows, cols, flag, threshold);
}

private int helper(int i, int j, int rows, int cols, int[][] flag, int threshold) {
    if (i < 0 || i >= rows || j < 0 || j >= cols ||
            numSum(i) + numSum(j) > threshold || flag[i][j] == 1)
        return 0;
    flag[i][j] = 1;
    return helper(i - 1, j, rows, cols, flag, threshold)
            + helper(i + 1, j, rows, cols, flag, threshold)
            + helper(i, j - 1, rows, cols, flag, threshold)
            + helper(i, j + 1, rows, cols, flag, threshold) + 1;
}

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

已完结!

猜你喜欢

转载自blog.csdn.net/baiye_xing/article/details/78428396