《剑指Offer》笔记4.4

分解让复杂问题简单化

  1. 复杂链表的复制
  2. 二叉搜索树与双向链表
  3. 序列化二叉树
  4. 字符串的排列

35:复杂链表的复制

题目:
请实现函数复制一个复杂链表。在复杂链表中,每个节点除了有一个指针指向下一个节点,还有一个指针指向链表中的任意节点或者 null。
思路:

  • 方法一,时间复杂度O(n2):
    a. 复制原始链表上的每个节点,并用next 链接起来;
    b. 设置每个节点的 random 指针。由于随机指针指向的位置可能在前面,也可能在后面,因此要从头节点开始找。
  • 方法二,时间复杂度为O(n),但是需要一个大小为O(n) 的哈希表:
    a. 复制原始链表上的每个节点,并用next 链接起来,同时把<被复制节点,复制节点>的配对信息放到哈希表中;
    b. 设置每个节点的 random 指针,用哈希表,通过指针的key,查找复制节点。
  • 方法三,
    a. 复制每个节点,将复制节点插到被复制节点后面;
    b. 重新遍历链表,复制老结点的随机指针给新结点,如A1.random = A.random.next;
    c. 拆分链表,将链表拆分为原链表和复制后的链表
/*
// Definition for a Node.
class Node {
    int val;
    Node next;
    Node random;
    public Node(int val) {
        this.val = val;
        this.next = null;
        this.random = null;
    }
}
*/
class Solution {
    public Node copyRandomList(Node head) {
        if(head==null) return null;
        
        Node curNode = head;
        // 1. 遍历链表,复制每个节点,将复制节点插到被复制节点后面
        while(curNode != null){
            Node cloneNode = new Node(curNode.val);
            cloneNode.next = curNode.next;
            curNode.next = cloneNode;
            curNode = cloneNode.next;
        }
        curNode = head;
        //2. 重新遍历链表,复制老结点的随机指针给新结点,如A1.random = A.random.next;
        while(curNode != null){
            curNode.next.random = curNode.random==null?null:curNode.random.next;
            curNode = curNode.next.next;
        }
        curNode = head;
        Node cloneHead = head.next;
        //3. 拆分链表,将链表拆分为原链表和复制后的链表
        while(curNode != null){
            Node cloneNode = curNode.next;
            curNode.next = cloneNode.next;
            cloneNode.next = cloneNode.next==null?null:cloneNode.next.next;
            curNode = curNode.next;
        }
        return cloneHead;
    }
}

36:二叉搜索树与双向链表

题目:
输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的循环双向链表。要求不能创建任何新的节点,只能调整树中节点指针的指向。
思路:
• 节点从小到大排序,因此应使用中序遍历
• 在构建相邻节点(设前驱节点 prepre ,当前节点 curcur )关系时,pre.right = cur,且cur.left = pre
• 设链表头节点 head 和尾节点 tail ,则应构建 head.left = tail 和 tail.right = head

/*
// Definition for a Node.
class Node {
    public int val;
    public Node left;
    public Node right;
    public Node() {}
    public Node(int _val) {
        val = _val;
    }
    public Node(int _val,Node _left,Node _right) {
        val = _val;
        left = _left;
        right = _right;
    }
};
*/
class Solution {
    Node head;
    Node pre;
    public Node treeToDoublyList(Node root) {
        if(root == null) return null;
        dfs(root);
        head.left = pre;
        pre.right = head;
        return head;
    }
    private void dfs(Node node){
        if(node==null)return;
        dfs(node.left);
        if(pre==null) head=node;
        else pre.right = node;
        node.left = pre;
        pre = node;
        dfs(node.right);
    }
}

37:序列化二叉树

题目:
请实现两个函数,分别用来序列化和反序列化二叉树。
思路:
• 序列化 serialize:借助队列,对二叉树做层序遍历,并将越过叶节点的 null 也打印出来
• 反序列化 deserialize:“ node, node.left, node.right ” 在序列化列表中的位置关系,可实现反序列化。利用队列按层构建二叉树,借助一个指针 i 指向节点 node 的左、右子节点,每构建一个 node 的左、右子节点,指针 i 就向右移动 1 位

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
public class Codec {
    // Encodes a tree to a single string.
    public String serialize(TreeNode root) {
        if (root==null) return "[]";
        StringBuilder res = new StringBuilder("[");
        Queue<TreeNode> queue = new LinkedList<>();
        queue.add(root);
        while (!queue.isEmpty()) {
            TreeNode node = queue.poll();
            if (node!=null) {
                res.append(node.val + ",");
                queue.add(node.left);
                queue.add(node.right);
            } else {
                res.append("null,");
            }
        }
        res.deleteCharAt(res.length() - 1);
        res.append("]");
        // System.out.println(res.toString());
        return res.toString();
    }
    // Decodes your encoded data to tree.
    public TreeNode deserialize(String data) {
        if(data.equals("[]")) return null;
        String[] vals = data.substring(1, data.length() - 1).split(",");
        TreeNode root = new TreeNode(Integer.parseInt(vals[0]));
        int i = 1;
        Queue<TreeNode> queue = new LinkedList<>();
        queue.add(root);
        while (!queue.isEmpty()) {
            TreeNode node = queue.poll();
            if (!vals[i].equals("null")) {
                node.left = new TreeNode(Integer.parseInt(vals[i]));
                queue.add(node.left);
            }
            i++;
            if (!vals[i].equals("null")) {
                node.right = new TreeNode(Integer.parseInt(vals[i]));
                queue.add(node.right);
            }
            i++;
        }
        return root;
    }
}
// Your Codec object will be instantiated and called as such:
// Codec codec = new Codec();
// codec.deserialize(codec.serialize(root));

38:字符串的排列

题目:
输入一个字符串,打印出该字符串中字符的所有排列。例如,输入字符串abc,则打印出由字符a、b、c所能排列出来的所有字符串abc、acb、bac、bca、cab和cba。
思路:
对于一个长度为 n 的字符串(假设字符互不重复),其排列共有 n×(n−1)×(n−2)…×2×1 种方案,根据字符串排列的特点,考虑深度优先搜索所有排列方案。即通过字符交换,先固定第 1 位字符( n 种情况)、再固定第 2 位字符(n−1 种情况)、……、最后固定第 n 位字符( 1 种情况);当字符串存在重复字符时,排列方案中也存在重复方案,为排除重复方案,需在固定某位字符时,保证 “每种字符只在此位固定一次” ,即遇到重复字符时不交换,直接跳过

class Solution {
    public String[] permutation(String s) {
        ArrayList<String> res = new ArrayList<>();
        dfs(s.toCharArray(), res, 0);
        return res.toArray(new String[res.size()]);
    }
    private void dfs(char[] chars, ArrayList<String> res, int i){
        if(i == chars.length-1){
            res.add(new String(chars));
            return;
        }
        HashSet<Character> set = new HashSet<>(); //去重
        for(int j=i;j<chars.length;j++){
            if(set.contains(chars[j])) continue;
            set.add(chars[j]);
            swap(chars,i,j);
            dfs(chars,res,i + 1);
            swap(chars,i,j);
        }
    }
    private void swap(char[] chars, int i, int j){
        char t = chars[j];
        chars[j] = chars[i];
        chars[i] = t;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值