【刷题笔记/剑指Offer】51-61

1. 字符流中第一个不重复的字符

请实现一个函数用来找出字符流中第一个只出现一次的字符。例如,当从字符流中只读出前两个字符”go”时,第一个只出现一次的字符是”g”。当从该字符流中读出前六个字符“google”时,第一个只出现一次的字符是”l”。

如果当前字符流没有存在出现一次的字符,返回#字符。

看了一会儿也没搞懂这到底要干嘛,感觉给的函数头有点儿莫名其妙,就自己写了个类,借助hashmap存储出现的次数,附带测试函数:

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

public interface FirstAppearingOnce {
    Map<Character, Integer> map = new HashMap<Character, Integer>();

    public static char firstAppearingOnce(String string) {
        char[] set = string.toCharArray();
        // first loop to build up the map
        for(int i = 0; i < set.length; i++) {
            if(map.containsKey(set[i])) {
                map.put(set[i], map.get(set[i])+1);
            } else {
                map.put(set[i], 1);
            }
        }
        //second loop to find the first appearing once character
        for(int i = 0; i < set.length; i++) {
            if(map.get(set[i]) == 1)
                return set[i];
        }
        return '#';
    }

    public static void main(String[] args) {
        String string = "asdfghjklasdfghjl";
        System.out.println(firstAppearingOnce(string));
    }
}

2. 链表中环的入口结点

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

最初的思路是:初始化两个指针,对每个指针1,指针2从它的下一个开始,往下走,如果两者相遇,那就返回指针1.这样算法复杂度是O(n^2)。
参考了一下书中的思路:先得到环的结点个数n,再将指针2前移n位,接下来让指针1和指针2以相同速度往前移动,它们相遇的时候,指针2已经跑完了一圈回来了,这样就找到了入口节点。
至于环结点个数n怎么求:初始化两个指针p1, p2,它们速度不同,先求出相遇的结点,然后计数从头结点开始到该节点的步数。

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

    ListNode(int val) {
        this.val = val;
    }
}
*/
public class Solution {
public static ListNode meetingNode(ListNode head) {
        if(head==null)
            return null;

        ListNode slow = head.next;
        if(slow==null)
            return null;

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

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

    public ListNode EntryNodeOfLoop(ListNode pHead) {
        ListNode meetingNode=meetingNode(pHead);
        if(meetingNode==null)
            return null;
//      得到环中的节点个数
        int nodesInLoop=1;
        ListNode p1=meetingNode;
        while(p1.next!=meetingNode){
            p1=p1.next;
            ++nodesInLoop;
        }
//      移动p1
        p1=pHead;
        for(int i=0;i<nodesInLoop;i++){
            p1=p1.next;
        }
//      移动p1,p2
        ListNode p2=pHead;
        while(p1!=p2){
            p1=p1.next;
            p2=p2.next;
        }
        return p1;
    }
}

3. 删除链表中重复的结点

在一个排序的链表中,存在重复的结点,请删除该链表中重复的结点,重复的结点不保留,返回链表头指针。 例如,链表1->2->3->3->4->4->5 处理后为 1->2->5

需要注意的是,这里要考虑到头节点也可能重复的情况。那么引入一个头节点之前的备用节点就显得很巧妙的解决了这个问题,这个思路和C++中链表的空链表头很像。代码如下:

/*
 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) return null;
        ListNode p = pHead;
        ListNode n = new ListNode(0);
        ListNode pre = n;
        n.next = pHead;
        boolean flag = false;
        while (p != null) {
            ListNode q = p.next;
            if (q == null) break;
            if (q.val == p.val) {
                while (q != null && q.val == p.val) {
                    q = q.next;
                }
                pre.next = q;
                p = q;
            } else {
                if (!flag) {
                    n.next = p;
                    flag = true;
                }
                pre = p;
                p = q;
            }
        }
        return n.next;
    }
}

4. 二叉树的下一个结点

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

额,第一次试试这个flowchart,画的有点儿丑,大伙儿将就着看。。基本逻辑已经表述清楚了。

Created with Raphaël 2.1.0 开始 pNode!=null? 有右子树? 返回右子树最左子结点 结束 是左结点? 返回父结点 返回最后一个左父结点的右父结点 yes no yes no yes no
/*
public class TreeLinkNode {
    int val;
    TreeLinkNode left = null;
    TreeLinkNode right = null;
    TreeLinkNode next = null;

    TreeLinkNode(int val) {
        this.val = val;
    }
}
*/
public class Solution {
    public TreeLinkNode GetNext(TreeLinkNode pNode)
    {
        if(pNode == null)
            return pNode;        
        TreeLinkNode nextNode = null;
        if(pNode.right != null) {//结点有右子树
            nextNode = pNode.right;
            while(nextNode.left != null)
                nextNode = nextNode.left;
        } else {
            if(pNode.next == null) {//结点无右子树,为根结点,无父结点
                nextNode = null;
            } else {
                if(pNode.next.left == pNode) {//结点无右子树,是父结点的左结点
                    nextNode = pNode.next;
                } else if(pNode.next.right == pNode) {//结点无右子树,是父结点的右结点
                    while(pNode.next != null) {
                        nextNode = pNode.next;
                        if(nextNode.left == pNode)
                            return nextNode;
                        pNode = pNode.next;
                    }
                    nextNode = null;
                }
            } 
        } 
        return nextNode;
    }
}

5. 对称的二叉树

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

只能说递归思想要掌握。。

/*
public class TreeNode {
    int val = 0;
    TreeNode left = null;
    TreeNode right = null;

    public TreeNode(int val) {
        this.val = val;

    }

}
*/
public class Solution {
    boolean isSymmetrical(TreeNode pRoot) {
        return isSymmetrical(pRoot, pRoot);
    }

    boolean isSymmetrical(TreeNode pRoot1, TreeNode pRoot2) {
        if(pRoot1 == null && pRoot2 == null)
            return true;
        if(pRoot1 == null || pRoot2 == null)
            return false;
        if(pRoot1.val != pRoot2.val)
            return false;

        return isSymmetrical(pRoot1.left, pRoot2.right)
            && isSymmetrical(pRoot1.right, pRoot2.left);
    }
}

6. 按之字形顺序打印二叉树

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

思路是以广度优先遍历算法基础,每行用一个flag记录是该正向打印还是反向打印,代码如下:

/*按层序遍历分层打印的代码,添加一段判断用以倒序输出即可*/
    public ArrayList<ArrayList<Integer>> Print(TreeNode pRoot) {
        ArrayList<ArrayList<Integer>> result = new ArrayList<ArrayList<Integer>>();
        if(pRoot == null){
            return result;
        }
        boolean leftToRight = true;
        Queue<TreeNode> layer = new LinkedList<TreeNode>();
        ArrayList<Integer> layerList = new ArrayList<Integer>();
        layer.add(pRoot);
        int start = 0, end = 1;
        while(!layer.isEmpty()){
            TreeNode cur = layer.remove();
            layerList.add(cur.val);
            start++;
            if(cur.left!=null){
                layer.add(cur.left);          
            }
            if(cur.right!=null){
                layer.add(cur.right);
            }
            if(start == end){
                end = layer.size();
                start = 0;             
                if(!leftToRight){
                    result.add(reverse(layerList));
                }else{
                    result.add(layerList);
                }
                leftToRight = !leftToRight;
                layerList = new ArrayList<Integer>();
            }
        }
        return result;
    }
    private ArrayList reverse(ArrayList<Integer> layerList) {    
        int length = layerList.size();
        ArrayList<Integer> reverseList = new ArrayList<Integer>();
        for(int i = length-1; i >= 0;i--){
            reverseList.add(layerList.get(i));
        }
        return reverseList;
    }

7. 把二叉树打印成多行

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

利用队列完成广度优先遍历,不过这里因为要按行打印,所以需要借助两个变量,curr(记录本行还有多少结点要打印)及next(记录下行还有多少结点要打印)来辅助:

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

/*
public class TreeNode {
    int val = 0;
    TreeNode left = null;
    TreeNode right = null;

    public TreeNode(int val) {
        this.val = val;

    }

}
*/
public class Solution {
    ArrayList<ArrayList<Integer>> Print(TreeNode pRoot) {
        ArrayList<ArrayList<Integer>> result = new ArrayList<ArrayList<Integer>>();
        if(pRoot == null) 
            return result;
        ArrayList<Integer> tmp = new ArrayList<Integer>();
        LinkedList<TreeNode> queue = new LinkedList<TreeNode>();

        queue.add(pRoot);
        int curr = 1;
        int next = 0;
        while(!queue.isEmpty()) {
            TreeNode node = queue.remove();
            curr--;
            tmp.add(node.val);

            if(node.left != null) {
                queue.add(node.left);
                next++;
            }
            if(node.right != null) {
                queue.add(node.right);
                next++;
            }

            if(curr == 0) {
                result.add(new ArrayList(tmp));
                tmp.clear();
                curr = next;
                next = 0;
            }
        }
        return result;
    }
}

8. 序列化二叉树

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

序列化二叉树,在这讲白了就是把二叉树化为字符串,反序列化二叉树就是将字符串还原为二叉树。定义以“,”分隔,null为“$”,代码如下:

/*
public class TreeNode {
    int val = 0;
    TreeNode left = null;
    TreeNode right = null;

    public TreeNode(int val) {
        this.val = val;

    }

}
*/
public class Solution {
    String Serialize(TreeNode root) {
        if(root == null)
            return "";
        StringBuffer buffer = new StringBuffer();
        Serialize(root, buffer);
        return buffer.toString();
    }
    void Serialize(TreeNode root, StringBuffer buffer) {
        if(root == null) {
            buffer.append("$,");
            return;
        }
        buffer.append(root.val + ",");
        Serialize(root.left, buffer);
        Serialize(root.right, buffer);
    }
    TreeNode Deserialize(String str) {
        if(str == null || str.length() == 0)
            return null;
        String[] strs = str.split(",");
        return addDeserialize(strs);
    }
    int index = -1;
    TreeNode addDeserialize(String[] strs) {
        index++;
        if(index < strs.length && !strs[index].equals("$")) {
            TreeNode node = new TreeNode(0);
            node.val = Integer.parseInt(strs[index]);
            node.left = addDeserialize(strs);
            node.right = addDeserialize(strs);
            return node;
        }
        return null;
    }

}

8. 二叉搜索树的第k个结点

给定一颗二叉搜索树,请找出其中的第k大的结点。例如, 5 / \ 3 7 /\ /\ 2 4 6 8 中,按结点数值大小顺序第三个结点的值为4。

注意到二叉搜索树中序遍历后便是从小到大排序的结果。所以用中序遍历。另外主要要强调的是,md第k大的数十从小到大第k个数的意思啊。。。anyway,代码如下:

/*
public class TreeNode {
    int val = 0;
    TreeNode left = null;
    TreeNode right = null;

    public TreeNode(int val) {
        this.val = val;

    }

}
*/
import java.util.ArrayList;

public class Solution {
    ArrayList<TreeNode> array = new ArrayList<TreeNode>();
    TreeNode KthNode(TreeNode pRoot, int k)
    {
        if(pRoot == null || k <= 0)
            return null;
        InorderTraverse(pRoot);
        if(array.size() >= k)
            return array.get(k - 1);
        return null;
    }
    //int count = 0;
    void InorderTraverse(TreeNode pRoot) {
        if(pRoot == null)
            return;
        if(pRoot.left != null)
            InorderTraverse(pRoot.left);
        array.add(pRoot);
        if(pRoot.right != null)
            InorderTraverse(pRoot.right);
    }
}

9. 滑动窗口的最大值

给定一个数组和滑动窗口的大小,找出所有滑动窗口里数值的最大值。例如,如果输入数组{2,3,4,2,6,2,5,1}及滑动窗口的大小3,那么一共存在6个滑动窗口,他们的最大值分别为{4,4,6,6,6,5}; 针对数组{2,3,4,2,6,2,5,1}的滑动窗口有以下6个: {[2,3,4],2,6,2,5,1}, {2,[3,4,2],6,2,5,1}, {2,3,[4,2,6],2,5,1}, {2,3,4,[2,6,2],5,1}, {2,3,4,2,[6,2,5],1}, {2,3,4,2,6,[2,5,1]}。

用双端队列,队列尾插入元素,若要插入的元素大于队列尾的元素,就删除队列尾的元素,如果队列头存储的index不在窗口内,就删除队列头。

码代码能力很需要提升,用IDE调了好一阵子才弄出来。。

import java.util.*;

public class Solution {
    public static ArrayList<Integer> maxInWindows(int [] num, int size)
    {
        ArrayList<Integer> result = new ArrayList<>();
        if(num.length == 0 || size <= 0)
            return result;
        else if(size > num.length){
            return result;
        }
        else if(size == 1) {
            for(int i = 0; i < num.length; i++)
                result.add(num[i]);
            return result;
        }
        Deque<Integer> deque = new ArrayDeque<Integer>();
        deque.addLast(0);
        for(int i = 1; i < size; i++) {
            while(!deque.isEmpty() && num[deque.peekLast()] < num[i])
                deque.removeLast();
            deque.addLast(i);
        }
        result.add(num[deque.peekFirst()]);
        for(int i = size; i < num.length; i++) {
            while(i - deque.peekFirst() >= size)
                deque.removeFirst();
            while(!deque.isEmpty() && num[deque.peekLast()] < num[i])
                deque.removeLast();
            deque.addLast(i);
            result.add(num[deque.peekFirst()]);
        }
        return result;
    }

    private static int findMax(int[] num) {
        int max = num[0];
        for(int i = 1; i < num.length; i++) {
            if(num[i] > max) 
                max = num[i];
        }
        return max;     
    }
}

10. 矩阵中的路径

请设计一个函数,用来判断在一个矩阵中是否存在一条包含某字符串所有字符的路径。路径可以从矩阵中的任意一个格子开始,每一步可以在矩阵中向左,向右,向上,向下移动一个格子。如果一条路径经过了矩阵中的某一个格子,则该路径不能再进入该格子。 例如 a b c e s f c s a d e e 矩阵中包含一条字符串”bcced”的路径,但是矩阵中不包含”abcb”路径,因为字符串的第一个字符b占据了矩阵中的第一行第二个格子之后,路径不能再次进入该格子。

回溯法解决该问题,试探前进,若不符合条件就退回。代码如下:

public class Solution {
    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[rows][cols];
        for(int i = 0; i < rows; i++) {
            for(int j = 0; j < cols; j++) {
                visited[i][j] = false;
            }
        }

        int pathLength = 0;

        for(int row = 0; row < rows; row++) {
            for(int col = 0; col < cols; col++) {
                if(hasPathCore(matrix, rows, cols, row, col, str, visited, pathLength))
                    return true;
            }
        }
        return false;
    }

    public boolean hasPathCore(char[] matrix, int rows, int cols, int row, int col, char[] str, 
            boolean[][] visited, int pathLength) {
        if(pathLength >= str.length)
            return true;
        boolean hasPath = false;
        if(row >= 0 && row < rows && col >= 0 && col < cols 
                && matrix[row * cols + col] == str[pathLength]
                && !visited[row][col]) {
            pathLength++;
            visited[row][col] = true;

            hasPath = hasPathCore(matrix, rows, cols, row - 1, col, str, visited, pathLength)
                    || hasPathCore(matrix, rows, cols, row + 1, col, str, visited, pathLength)
                    || hasPathCore(matrix, rows, cols, row, col - 1, str, visited, pathLength)
                    || hasPathCore(matrix, rows, cols, row, col + 1, str, visited, pathLength);

            if(!hasPath) {
                pathLength--;
                visited[row][col] = false;
            }
        }
        return hasPath;
    }
}

另外,eclipse的exception断点真的很好用,建议大家去学一下。

11. 机器人的运动范围

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

public class Solution { 
    public int movingCount(int threshold, int rows, int cols)
    {
        if(threshold < 1 || rows < 1 || cols < 1)
            return 0;
        boolean[][] visited = new boolean[rows][cols];
        for(int i = 0; i < rows; i++) {
            for(int j = 0; j < cols; j++) {
                visited[i][j] = false;
            }
        }


        int count = movingCountCore(threshold, rows, cols, 0, 0, visited);

        return count;
    }

    public int movingCountCore(int threshold, int rows, int cols, int row, int col, boolean[][] visited) {
        int count = 0;
        if(isQualified(threshold, rows, cols, row, col, visited)) {
            visited[row][col] = true;

            count = 1 + movingCountCore(threshold, rows, cols, row - 1, col, visited) +
                    movingCountCore(threshold, rows, cols, row + 1, col, visited) +
                    movingCountCore(threshold, rows, cols, row, col - 1, visited) +
                    movingCountCore(threshold, rows, cols, row, col + 1, visited);
        }
        return count;
    }

    public boolean isQualified(int threshold, int rows, int cols, int row, int col, boolean[][] visited) {
        if (row >= 0 && row < rows && col >= 0 && col < cols && !visited[row][col]
                && (sumOf(row) + sumOf(col)) <= threshold) {
            return true;
        }
        return false;
    }

    public int sumOf(int n) {
        int sum = 0;
        while(n != 0) {
            sum += n % 10;
            n /= 10;
        }
        return sum;
    }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值