剑指Offer编程整理(六)

本文精选了多个数据结构与算法的经典题目,包括链表、二叉树、字符串匹配等,提供了详细的解题思路与代码实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1、删除链表中重复的结点

2、二叉树的下一个结点

3、对称的二叉树

4、按之字形顺序打印二叉树

5、把二叉树打印成多行

6、序列化二叉树

7、二叉搜索树的第k个结点

8、数据流中的中位数

9、滑动窗口的最大值

10、矩阵中的路径

11、机器人的运动范围


1、删除链表中重复的结点

(1)问题描述:

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

(2)解题思路:

定义一个新的链表,让新链表头结点的下一个结点指向该链表的头结点;定义两个新结点,分别为新的链表的头头结点和原链表的头结点;在两个新结点中执行重复元素的覆盖赋值;返回新链表头结点.next。

(3)代码实现:

public class Solution {
    public ListNode deleteDuplication(ListNode pHead)
    {
        if (pHead == null)
            return null;
        ListNode node = new ListNode(0);
        node.next = pHead;
        ListNode pre = node;
        ListNode p = pHead;
        while (p!= null && p.next != null){
            if (p.val == p.next.val){
                int temp = p.val;
                while (p != null &&p.val == temp){
                    p = p.next;
                }
                pre.next = p;
            }else {
                pre = p;
                p = p.next;
            }
        }
        return node.next;
    }
}

2、二叉树的下一个结点

(1)问题描述:

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

(2)解题思路:

如果有右子树,则找右子树的最左节点;没右子树,则向上找第一个是父节点左孩子的节点,返回该节点父节点。

(3)代码实现:

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 == pNode.next.left)
                return pNode.next;
            pNode = pNode.next;
        }
        return null;
    }

3、对称的二叉树

(1)问题描述:

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

(2)解题思路:

左子树的左子树和右子树的右子树相同,左子树的右子树和右子树的左子树相同

(3)代码实现:

public class Solution {
    boolean isSymmetrical(TreeNode pRoot)
    {
        if(pRoot==null)
            return true;
        return comRoot(pRoot.left,pRoot.right);
    }
        public boolean comRoot(TreeNode left,TreeNode right){
            if(left==null)return right==null;
            if(right==null)return false;
            if(left.val!=right.val)return false;
           
            return comRoot(left.left,right.right)&&comRoot(left.right,right.left);
        }
    }

4、按之字形顺序打印二叉树

(1)问题描述:

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

(2)解题思路:

层序遍历;

遍历每一层时判断是否需要从右往左打印。

(3)代码实现:

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.Queue;
/*
public class TreeNode {
    int val = 0;
    TreeNode left = null;
    TreeNode right = null;

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

    }
}
*/
public class Solution {
    public ArrayList<ArrayList<Integer>> Print(TreeNode pRoot) {
        ArrayList<ArrayList<Integer>> lists = new ArrayList<ArrayList<Integer>>();
        if (pRoot == null){
            return lists;
        }
        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.size() > 0){
            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){
                    lists.add(reverse(layerList));
                }else {
                    lists.add(layerList);
                }

                leftToRight = !leftToRight;
                layerList = new ArrayList<Integer>();
            }
        }

        return lists;
    }
    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;
    }
}

5、把二叉树打印成多行

(1)问题描述:

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

(2)解题思路:

层序遍历

(3)代码实现:

public class Solution {
    ArrayList<ArrayList<Integer> > Print(TreeNode pRoot) {
    
        ArrayList<ArrayList<Integer>> result = new ArrayList<ArrayList<Integer>>();
    if(pRoot==null){
          return result;
      }
    LinkedList<TreeNode> link = new LinkedList<TreeNode>();
    ArrayList<Integer> list = new ArrayList<Integer>();
    link.add(pRoot);
    int start = 0, end = 1;
    while(!link.isEmpty()){
        TreeNode node = link.remove();
        list.add(node.val);
        start++;
        if(node.left!=null){
            link.add(node.left);
        }
        if(node.right!=null){
            link.add(node.right);
        }
        if(start == end){
            end = link.size();
            start = 0;
            result.add(list);
            list = new ArrayList<Integer>();
        }       
    }

    return result;
    }
}

6、序列化二叉树

(1)问题描述:

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

(2)解题思路:

根据给出的返回值类型可知,序列化就是:需要将一个二叉树序列化成为一个String类型;

反序列化就是将序列化返回的String字符串返回到原二叉树。

对于序列化:

使用前序遍历,递归的将二叉树的值转化为字符,并且在每次二叉树的结点不为空时,在转化val所得的字符之后添加一个','作为分割。对于空节点则以'#'代替。

对于反序列化:

按照前序顺序,递归的使用字符串中的字符创建一个二叉树。

(3)代码实现:

import java.util.*;
public class Solution {
    int index = -1;
    String Serialize(TreeNode root) {
        StringBuilder sb = new StringBuilder();
        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();

    }
    TreeNode Deserialize(String str) {
        index++;
        int length = str.length();
        if (index >= length){
            return null;
        }

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

        return node;
    }
}

7、二叉搜索树的第k个结点

(1)问题描述:

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

(2)解题思路:

二叉搜索树,中序遍历后可以得到一个升序序列;将升序序列存入一个栈中,pop出栈,取第k个结点返回。

(3)代码实现:

public class KthNodeInTree {
    
    TreeNode KthNode(TreeNode pRoot, int k) {

        if(pRoot==null) return null;
        int i = 0;
        Stack<TreeNode> stack = new Stack<TreeNode>();
        TreeNode p = pRoot;
        while(p!=null || stack.size()!=0){
            while(p!=null){
                stack.add(p);
                System.out.println("p:"+ p.val);
                p = p.left;

            }
            if(stack.size()!=0){
                p = stack.pop();
                if(++i==k){
                    return p;
                }
                System.out.println("p2:"+p.val);
                p = p.right;
            }
        }
        return null;
    }
另:

public class Solution {
    int index=0;
    TreeNode KthNode(TreeNode pRoot, int k)
    {
        if(pRoot!=null){
            TreeNode node=KthNode(pRoot.left,k);
            if(node!=null)
                return node;
            index++;
            if(index==k)
                return pRoot;
            node=KthNode(pRoot.right,k);
            if(node!=null)
                return node;
        }
        return null;
    }
}

8、数据流中的中位数

(1)问题描述:

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

(2)解题思路:

将输入数据流放进一个数组中,使用堆排序,对每个插入数据进行排序;最后返回数组的中位数;

(3)代码实现:

import java.util.Arrays;

public class Solution {

    static int[] seq = new int[0];
    public void Insert(Integer num) {
        //      int stop = list.size(), start = 0;
        int stop = seq.length-1, start = 0;
        if(seq.length == 0){
            seq = Arrays.copyOf(seq, 1);
            seq[0] = num;
        }else{
            while(stop >= start){
                int mid = (start+stop) >>> 1;
                if(seq[mid] < num){
                    start = mid + 1;
                }else if(seq[mid] > num){
                    stop = mid - 1;
                }else{ // key == mid
                    seq = Arrays.copyOf(seq, seq.length+1);
                    for(int i = seq.length-1;i > mid;i--){
                        seq[i] = seq[i-1];
                    }
                    seq[mid] = num;
                    break;
                }
            }
            if(start > seq.length-1){//num bigger than last key
                seq = Arrays.copyOf(seq, seq.length+1);
                seq[start] = num;
            }else if(stop < 0){ //num less than first key
                seq = Arrays.copyOf(seq, seq.length+1);
                for(int i = seq.length-1; i >0;i--){
                    seq[i] = seq[i-1];
                }
                seq[start] = num;
            }else{ // num in between the seq
                seq = Arrays.copyOf(seq, seq.length+1);
                for(int i = seq.length-1; i > start;i--){
                    seq[i] = seq[i-1];
                }
                seq[start] = num;
            }
        }
    }

    public Double GetMedian() {
        int len = seq.length;
        if(len %2 ==0){
            return (seq[len/2]+seq[len/2-1])/2.0;
        }
        return seq[len/2]*1.0;
    }
}

9、滑动窗口的最大值

(1)问题描述:

给定一个数组和滑动窗口的大小,找出所有滑动窗口里数值的最大值。例如,如果输入数组{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]}。

(2)解题思路:

循环嵌套,list

(3)代码实现:

import java.util.*;
public class Solution {
    public ArrayList<Integer> maxInWindows(int [] num, int size)
    {
        int len=num.length;
        ArrayList<Integer> list=new ArrayList<Integer>();
        if(len==0||size<1)
            return list;
        for(int i=0;i<=len-size;i++){
            int max=num[i];
            for(int j=i+1;(j-i)<size;j++){
                max=max>num[j]?max:num[j];
            }
            list.add(new Integer(max));
        }
        return list;
    }
}

10、矩阵中的路径

(1)问题描述:

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

(2)解题思路:

从矩阵某个外围位置开始,上下左右进行遍历查找,看是否能够匹配得到需要查找的字符串;使用回溯法上下左右进行递归尝试。

(3)代码实现:

public class Solution {
    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 static 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;
    }
}

11、机器人的运动范围

(1)问题描述:

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

(2)解题思路:

1.从(0,0)开始走,每成功走一步标记当前位置为true,然后从当前位置往四个方向探索,返回1 + 4 个方向的探索值之和。
2.探索时,判断当前节点是否可达的标准为:
1)当前节点在矩阵内;
2)当前节点未被访问过;
3)当前节点满足limit限制。

(3)代码实现:

public class Solution {
    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;
        do{
            sum += i%10;
        }while((i = i/10) > 0);
        return sum;
    }
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值