[Leetcode——数据结构]217. 存在重复元素

217. 存在重复元素

相关知识点:哈希表​​​​​​​

分析:此题不能直接用双循环暴力求解,或是关键字排序后再逐个比较前后元素是否相等,否则该算法的时间复杂度将超过O(nlogn)。

因此,考虑使用HashSet实现,不断在HashSet中添加元素,如果该集合中已经存在该元素就说明有重复元素,直接返回true就行。

class Solution {
    public boolean containsDuplicate(int[] nums) {
        HashSet set = new HashSet();
        for(int i = 0; i < nums.length; i++){
            if(set.contains(nums[i])){
                return true;
            }else{
                set.add(nums[i]);
            }
        }
        return false;
    }
}

​​​​​​​变式题1:

面试题 02.07. 链表相交

给你两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点。如果两个链表没有交点,返回 null 。

图示两个链表在节点 c1 开始相交

题目数据 保证 整个链式结构中不存在环。

注意,函数返回结果后,链表必须 保持其原始结构 。

示例 1:

输入:intersectVal = 8, listA = [4,1,8,4,5], listB = [5,0,1,8,4,5], skipA = 2, skipB = 3
输出:Intersected at '8'
解释:相交节点的值为 8 (注意,如果两个链表相交则不能为 0)。
从各自的表头开始算起,链表 A 为 [4,1,8,4,5],链表 B 为 [5,0,1,8,4,5]。
在 A 中,相交节点前有 2 个节点;在 B 中,相交节点前有 3 个节点。

示例 2:

输入:intersectVal = 2, listA = [0,9,1,2,4], listB = [3,2,4], skipA = 3, skipB = 1
输出:Intersected at '2'
解释:相交节点的值为 2 (注意,如果两个链表相交则不能为 0)。
从各自的表头开始算起,链表 A 为 [0,9,1,2,4],链表 B 为 [3,2,4]。
在 A 中,相交节点前有 3 个节点;在 B 中,相交节点前有 1 个节点。

示例 3:

输入:intersectVal = 0, listA = [2,6,4], listB = [1,5], skipA = 3, skipB = 2
输出:null
解释:从各自的表头开始算起,链表 A 为 [2,6,4],链表 B 为 [1,5]。
由于这两个链表不相交,所以 intersectVal 必须为 0,而 skipA 和 skipB 可以是任意值。
这两个链表不相交,因此返回 null 。

提示:

  • listA 中节点数目为 m
  • listB 中节点数目为 n
  • 0 <= m, n <= 3 * 10^4
  • 1 <= Node.val <= 10^5
  • 0 <= skipA <= m
  • 0 <= skipB <= n
  • 如果 listA 和 listB 没有交点,intersectVal 为 0
  • 如果 listA 和 listB 有交点,intersectVal == listA[skipA + 1] == listB[skipB + 1]

分析:此题即上一题的变式,将listA中的元素全存入HashSet中,再用listB在HashSet中判断有无重复的元素。注意:判断相等用的是结点的引用而非结点的值!若只判断值相等,则无法保证后续结点的重合。

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) {
 *         val = x;
 *         next = null;
 *     }
 * }
 */
public class Solution {
    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        HashSet set=new HashSet();
        for(ListNode head =headA;head!=null;head=head.next){
            set.add(head);
        }
        for(ListNode node=headB;node!=null;node=node.next){
            if(set.contains(node)) return node;
        }
        return null;
    }
}

进阶:你能否设计一个时间复杂度 O(n) 、仅用 O(1) 内存的解决方案?​​​​​​​

分析:在O(A + B)的时间和额外的O(1)空间中实现代码,说明不需要使用散列表即可达到目的。

分析链表的重合性:若两个链表有交点,则说明在当前交点后的所有结点都重合(next指针重合),因此,设第一个共同节点为node,链表A的长度为a,链表b的长度为b,两个链表的公共尾部节点长度c,则会有:

  • headA 到 node的距离为 a-c
  • headB 到 node 的距离为 b-c

根据这个性质,使用双指针法实现:nodeA和nodeB分别指向两链表的头结点。先将长度较长的链表走|listB.length-listA.length|步,再从当前结点开始,每走一步,判断两个指针的引用是否相等。

public class Solution {
    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        int lengthA=0,lengthB=0;
        for(ListNode node =headA;node!=null;node=node.next){
            lengthA++;
        }
        for(ListNode node=headB;node!=null;node=node.next){
            lengthB++;
        }
        ListNode nodeA=headA,nodeB=headB;
        if(lengthA>lengthB){
            for(int i=0;i<lengthA-lengthB;i++)
            nodeA=nodeA.next;
        }else{
            for(int i=0;i<lengthB-lengthA;i++)
            nodeB=nodeB.next;
        }
        while(nodeA!=nodeB){
            nodeA=nodeA.next;
            nodeB=nodeB.next;
        }
        return nodeA;
    }
}

参考一个很巧妙的写法:力扣

public class Solution {
    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        /*
        总体思路:双指针
        headA=[a1,a2,...,an]与headB=[b1,b2,...,bn]
        pA在a1出发,pB在b1出发;当pA到达A链表末尾null就到B开头处重新开始;pB同理
        最后两个指针相遇时就是相交的地方
         */
        ListNode pA = headA, pB = headB;
        while (pA != pB) {
            // A指针与B一路往下走,走完了就去另一个链表的头结点,直至相遇
            // 这里注意不能用pA.next作为判断,因为遇到空链表会发生空指针异常
            // 这里就算有其中一个链表为空或者没相交的也成立(最后为null)
            pA = pA == null ? headB : pA.next;
            pB = pB == null ? headA : pB.next;
        }
        // pA==pB
        return pA;
    }
}

变式题2:

1. 两数之和

 分析:若采用双循环暴力求解,则时间复杂度会到达O(n^2)。思考有没有其他方法?

采用哈希表HashMap求解,一种常规思想是构造整个数组的HashMap,再依次遍历整个HashMap找是否有互补元素存在于HashMap中。下给出一种巧妙的求解方法:由于每当我们为一个数搜索互补数时,我们已经知道所要求的互补数为target-curr。因此,不妨将target-curr存入HashMap中,在每一次遍历到新元素时,我们仅需要找在HashMap

class Solution {
    public int[] twoSum(int[] nums, int target) {
        // 建立k-v ,一一对应的哈希表
        HashMap<Integer,Integer> map=new HashMap();
        for(int i=0;i<nums.length;i++){
            if(map.containsKey(nums[i])) {
                int[] res={map.get(nums[i]),i};
                return res;
            }
            else map.put(target-nums[i],i);
        }
        return null;
    }
}

此题的思想(找补)的其他应用:

20. 有效的括号

 

import java.util.Stack;
class Solution {
    public boolean isValid(String s) {
        Stack<Character> stack=new Stack();
        for(char c:s.toCharArray()){
            if(c=='(')stack.push(')');
            else if(c=='[')stack.push(']');
            else if(c=='{')stack.push('}');
            else if(stack.isEmpty()||c!=stack.pop()) return false;
        }
        return stack.isEmpty();
    }
}

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值