【D17】零钱兑换&环形链表Ⅰ/Ⅱ (LC 322&141&142)

322. 零钱兑换

问题描述

给定不同面额的硬币 coins 和一个总金额 amount。编写一个函数来计算可以凑成总金额所需的最少的硬币个数。如果没有任何一种硬币组合能组成总金额,返回 -1。
你可以认为每种硬币的数量是无限的。

解题思路

动态规划,dp[i]表示凑成总金额为i所需要的最少硬币个数

代码实现

class Solution {
    public int coinChange(int[] coins, int amount) {
        if(amount < 0){
            return -1;
        }

        //dp[i]表示凑成总金额为i所需要的最少硬币个数
        int[] dp = new int[amount + 1];
        dp[0] = 0;
        for(int i = 1; i <= amount; i++){
            //凑成amount金额的硬币数量最多为amount个,初始化dp[i]为amount+1等于初始化为正无穷,便于后续取最小值
            dp[i] = amount + 1;
            for(int coin : coins){
                if(i < coin){
                    continue;
                }else{
                    dp[i] = Math.min(dp[i], dp[i - coin] + 1);
                }
            }
        }

        if(dp[amount] == amount + 1){
            return -1;      
        }else{
            return dp[amount];
        }
        
    }
}

141. 环形链表

问题描述

给定一个链表,判断链表中是否有环。
如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1,则在该链表中没有环。注意:pos 不作为参数进行传递,仅仅是为了标识链表的实际情况。
如果链表中存在环,则返回 true 。 否则,返回 false 。

解题思路

快慢指针同时从起点出发,若链表有环,必两个指针一定会相遇。
注意,因为初始情况两个指针都位于头节点,所以应该让指针先出发,然后再判断指针的位置是否指向同一个节点。

代码实现

/**
 * Definition for singly-linked list.
 * class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) {
 *         val = x;
 *         next = null;
 *     }
 * }
 */
public class Solution {
    public boolean hasCycle(ListNode head) {
        if(head == null || head.next == null){
            return false;
        }
        ListNode slow = head, fast = head;
        while(fast != null && fast.next != null){
            slow = slow.next;
            fast = fast.next.next;
            if(slow == fast){
                return true;
            } 
        }
        return false;   
    }
}

142. 环形链表 II

问题描述

给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。

解题思路-哈希集合

采用HashSet来保存已访问过的节点,第一个被再次访问的节点即为开始入环的第一个节点。


HashSet主要方法

代码实现-哈希集合

public class Solution {
    public ListNode detectCycle(ListNode head) {
        Set set = new HashSet<>();
        while(head != null){
            if(set.contains(head)){
                return head;
            }
            set.add(head);
            head = head.next;
        }
        return null;
    }
}

解题思路-双指针

接着上一题的思路,当快慢指针相遇时,让一个新的慢指针指向头节点。然后新旧慢指针同时从头节点、相遇点出发,再次相遇的位置就是入环的第一个节点。
假设链表环前有 a 个节点,环内有 b 个节点,相遇点再走到环的起点有c个节点。
1.第一次相遇时
慢指针路程s=a+b-c
快指针路程是慢指针路程的两倍2s= a+n*b+b-c,其中b为快指针完整走过的环的总圈数;
两式相减得到s=n*b,因此 a+b-c = n*b,可得a=(n-1)b + c
2.第二次相遇时
旧的慢指针走过的路程:(n-1)b + c
新的慢指针走过的路程:a

代码实现-双指针

/**
 * Definition for singly-linked list.
 * class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) {
 *         val = x;
 *         next = null;
 *     }
 * }
 */
public class Solution {
    public ListNode detectCycle(ListNode head) {
        //无环,返回null
        if(head == null || head.next == null){
            return null;
        }
        //判断链表是否有环
        boolean hasCycle = false;
        ListNode slow = head, fast = head;
        while(fast != null && fast.next != null){
            slow = slow.next;
            fast = fast.next.next;
            if(slow == fast){
                //快慢指针第一次相遇时退出循环
                hasCycle = true;
                break;
            } 
        }

        //无环,返回null
        if(!hasCycle){
            return null;
        }
        //让原来的快指针指向头节点,成为新的慢指针
        fast = head;
        while(fast != slow){
            fast = fast.next;
            slow = slow.next;
        }
        return slow;  
    }
}

你可能感兴趣的:(【D17】零钱兑换&环形链表Ⅰ/Ⅱ (LC 322&141&142))