LeetCode 刷题 第八天

1. 路径总和 II

给你二叉树的根节点 root 和一个整数目标和 targetSum ,找出所有 从根节点到叶子节点 路径总和等于给定目标和的路径。

分析:

dfs. 每次进入到下一个节点时动态的调整targetSum, 当targetSum==0 && 该节点为叶子节点时该路径为目标路径.

class Solution {
    List<List<Integer>> res = new ArrayList<>();
    Deque<Integer> deque = new LinkedList<>();
    public List<List<Integer>> pathSum(TreeNode root, int targetSum) {
        dfs(root, targetSum);
        return res;
    }

    public void dfs(TreeNode root, int ts){
        if (root == null){
            return;
        }
        deque.offerLast(root.val);
        ts -= root.val;
        if (ts==0 && root.left==null && root.right==null){
            res.add(new LinkedList<>(deque));
            deque.pollLast();
            return;
        }
        dfs(root.left, ts);
        dfs(root.right, ts);
        deque.pollLast();


    }
}

2. 摆动序列

给你一个整数数组 nums,将它重新排列成 nums[0] < nums[1] > nums[2] < nums[3]... 的顺序。

你可以假设所有输入数组都可以得到满足题目要求的结果。

分析:

首先要利用快速选择来找到nums的中位数m,把nums依照m分成两个子数组A,B,A里面的数小于m,B里面的数都大于m. 一般可以想到A,B相互穿插就会得到结果,例如:对于数组[1, 5, 2, 4, 3],我们将其排序,得到[1, 2, 3, 4, 5],然后将其分割为[1, 2, 3]和[4, 5],对两个数组进行穿插,得到[1, 4, 2, 5, 3]。但这种想法存在问题,例如:对于数组[1, 2, 2, 3],按照这种做法求得的结果仍为[1, 2, 2, 3]。如果题目不要求各元素严格大于或小于相邻元素,即,只要求nums[0] <= nums[1] >= nums[2] <= nums[3]...,那么这一解法是符合要求的,但题目要求元素相互严格大于或小于,那么需要稍微做一点改进。出现上述情况其实是因为数组A和数组B出现了相同元素,用r来表示这一元素。可以很容易发现,如果A和B都存在r,那么r一定是A的最大值,B的最小值,这意味着r一定出现在A的尾部,B的头部。其实,如果这一数字的个数较少,不会出现这一现象,只有当这一数字个数达到原数组元素总数的一半,才会在穿插后的出现在相邻位置。例如,对于数组[1,1,2,2,3,3],分割为[1,1,2]和[2,3,3],虽然A和B都出现了2,但穿插后为[1,2,1,3,2,3],满足要求。而如果2的个数再多一些,即[1,1,2,2,2,3],分割为[1,1,2]和[2,2,3],最终结果为[1,2,1,2,2,3],来自A的2和来自B的2出现在了相邻位置。出现这一问题是因为重复数在A和B中的位置决定的,因为r在A尾部,B头部,所以如果r个数太多(大于等于(length(nums) + 1)/2),就可能在穿插后相邻。要解决这一问题,要使A的r和B的r在穿插后尽可能分开。一种可行的办法是将A和B反序:

例如,对于数组[1,1,2,2,2,3],分割为[1,1,2]和[2,2,3],分别反序后得到[2, 1, 1]和[3, 2, 2],此时2在A头部,B尾部,穿插后就不会发生相邻了。

当然,这只能解决r的个数等于(length(nums) + 1)/2的情况,如果r的个数大于(length(nums) + 1)/2,还是会出现相邻。但实际上,这种情况是不存在有效解的,也就是说,这种数组对于本题来说是非法的。

class Solution {
    public void wiggleSort(int[] nums) {
        int[] arr = nums.clone();
        Arrays.sort(arr);
        int n = nums.length;
        int x = (n + 1) / 2;
        for (int i = 0, j = x - 1, k = n - 1; i < n; i += 2, j--, k--) {
            nums[i] = arr[j];
            if (i + 1 < n) {
                nums[i + 1] = arr[k];
            }
        }
    }
}

3. 复杂链表的复制

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

class Node {
    int val;
    Node next;
    Node random;

    public Node(int val) {
        this.val = val;
        this.next = null;
        this.random = null;
    }
}

分析:

因为复杂列表有一个random指针,无法像传统链表一样边遍历边复制,利用哈希表实现。首先建立一个哈希表Map<Node,Node> map, <key,value>分别代表原节点和复制的节点,第一遍遍历后首先把val值赋值给复制后的节点,第二遍遍历时再把random,next赋值给复制后的节点,最后返回 map.get(head)即可.

   class Solution {
        public Node copyRandomList(Node head) {
            if (head == null) return null;
            HashMap<Node, Node> hashMap = new HashMap<>();
            Node cur = head;
            while (cur != null) {
                Node node = new Node(cur.val);
                hashMap.put(cur, node);
                cur = cur.next;
            }
            cur = head;
            while (cur != null) {
                hashMap.get(cur).next = hashMap.get(cur.next);
                hashMap.get(cur).random = hashMap.get(cur.random);
                cur = cur.next;
            }
            return hashMap.get(head);

        }
    }

 4. 二叉搜索树的双向链表

 输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的循环双向链表。要求不能创建任何新的节点,只能调整树中节点指针的指向。

我们希望将这个二叉搜索树转化为双向循环链表。链表中的每个节点都有一个前驱和后继指针。对于双向循环链表,第一个节点的前驱是最后一个节点,最后一个节点的后继是第一个节点。

下图展示了上面的二叉搜索树转化成的链表。“head” 表示指向链表中有最小元素的节点。

 

特别地,我们希望可以就地完成转换操作。当转化完成以后,树中节点的左指针需要指向前驱,树中节点的右指针需要指向后继。还需要返回链表中的第一个节点的指针。

分析:

有题意可知,返回的双向链表是递增的,而二叉搜索树的中序遍历为一个递增的序列。所以利用中序遍历,首先定义一个节点pre用来定义当前节点的前一个节点,如果pre==null,则说明当前节点node为头结点,需要记录下头结点,pre.right=node,node.left=pre, pre=node. 当遍历结束时pre代表最后一个节点,需要pre.right = head, head.left = pre.

class Solution {
    Node pre, head;
    public Node treeToDoublyList(Node root) {
        if (root==null) return null;
        dfs(root);
        pre.right = head;
        head.left = pre;
        return head;
    }

    public void dfs(Node node){
        if (node==null) return;
        dfs(node.left);
        if (pre != null){
            pre.right = node;
        }else {
           head=node;//记录下头结点
        }
        node.left = pre;
        pre = node;
        dfs(node.right);
    }
}

5.字符串的排列

输入一个字符串,打印出该字符串中字符的所有排列。你可以以任意顺序返回这个字符串数组,但里面不能有重复元素。

分析:

根据字符串排列的特点,考虑深度优先搜索所有排列方案。即通过字符交换,先固定第 1位字符( n种情况)、再固定第 2 位字符( n-1 种情况)、... 、最后固定第 n 位字符(1 种情况)。当字符串存在重复字符时,排列方案中也存在重复的排列方案。为排除重复方案,需在固定某位字符时,保证 “每种字符只在此位固定一次” ,即遇到重复字符时不交换,直接跳过。从 DFS 角度看,此操作称为 “剪枝” 。当x==len-1时,递归终止,把得到的字符串组合c添加到res,并返回;

递推工作:

  1. 初始化一个 Set ,用于排除重复的字符;将第 x 位字符与 i ∈ [x, len(c)] 字符分别交换,并进入下层递归;
  2. 剪枝: 若 c[i] 在 Set​ 中,代表其是重复字符,因此 “剪枝” ;
  3. 将 c[i] 加入 Set​ ,以便之后遇到重复字符时剪枝;
  4. 固定字符: 将字符 c[i] 和 c[x] 交换,即固定 c[i] 为当前位字符;
  5. 开启下层递归: 调用 dfs(x + 1) ,即开始固定第 x + 1 个字符;
  6. 还原交换: 将字符 c[i] 和 c[x] 交换(还原之前的交换);
class Solution {
    List<String> res = new LinkedList<>();
    char[] c;
    public String[] permutation(String s) {
        c = s.toCharArray();
        dfs(0);
        return res.toArray(new String[res.size()]);
    }
    void dfs(int x) {
        if(x == c.length - 1) {
            res.add(String.valueOf(c));      // 添加排列方案
            return;
        }
        HashSet<Character> set = new HashSet<>();
        for(int i = x; i < c.length; i++) {
            if(set.contains(c[i])) continue; // 重复,因此剪枝
            set.add(c[i]);
            swap(i, x);                      // 交换,将 c[i] 固定在第 x 位
            dfs(x + 1);                      // 开启固定第 x + 1 位字符
            swap(i, x);                      // 恢复交换
        }
    }
    void swap(int a, int b) {
        char tmp = c[a];
        c[a] = c[b];
        c[b] = tmp;
    }
}

 6. 数组中出现次数超过一半的数字

数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。

分析:

使用哈希表在遍历数组的时候记录下每个数出现的次数。

class Solution {
    private Map<Integer, Integer> countNums(int[] nums) {
        Map<Integer, Integer> counts = new HashMap<Integer, Integer>();
        for (int num : nums) {
            if (!counts.containsKey(num)) {
                counts.put(num, 1);
            } else {
                counts.put(num, counts.get(num) + 1);
            }
        }
        return counts;
    }

    public int majorityElement(int[] nums) {
        Map<Integer, Integer> counts = countNums(nums);

        Map.Entry<Integer, Integer> majorityEntry = null;
        for (Map.Entry<Integer, Integer> entry : counts.entrySet()) {
            if (majorityEntry == null || entry.getValue() > majorityEntry.getValue()) {
                majorityEntry = entry;
            }
        }

        return majorityEntry.getKey();
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值