哈希表的查找、插入及删除——166、138 哈希表与索引——1、167、599、219、220 (3简3中1难)

166. 分数到小数(中等)

给定两个整数,分别表示分数的分子 numerator 和分母 denominator,以 字符串形式返回小数 。

如果小数部分为循环小数,则将循环的部分括在括号内。

如果存在多个答案,只需返回 任意一个 。

对于所有给定的输入,保证 答案字符串的长度小于 10^4 。

解法一、哈希

见注释。这里最有趣的地方有两个,一个是用%来判断,既满足了判断商是整数的情况,又满足了分子或者分母一方是0的情况;一个是循环迭代记余数。

class Solution {
    public String fractionToDecimal(int numerator, int denominator) {
        long numeratorLong = (long) numerator;
        long denominatorLong = (long) denominator;
        //处理是整数或者是0的情况
        if(numeratorLong % denominatorLong == 0){
            return String.valueOf(numeratorLong / denominatorLong);
        }
        //处理负数的情况
        StringBuffer sb = new StringBuffer();
        if(numeratorLong < 0 ^ denominatorLong < 0)sb.append('-');
        //整数部分
        numeratorLong = Math.abs(numeratorLong);
        denominatorLong = Math.abs(denominatorLong);
        long integerPart = numeratorLong / denominatorLong;
        sb.append(integerPart);
        sb.append('.');
        //小数部分
        StringBuffer fractionPart = new StringBuffer();
        Map<Long,Integer> remainderIndexMap = new HashMap<>();
        long remainder = numeratorLong % denominatorLong;
        int index = 0;
        while(remainder != 0 && !remainderIndexMap.containsKey(remainder)){
            remainderIndexMap.put(remainder,index);
            remainder *= 10;
            fractionPart.append((remainder / denominatorLong));
            remainder %= denominatorLong;
            index++;
        }
        //若不是0,则while循环里不满足第二个条件,则记录位置并且循环
        if(remainder!=0){
            int insertIndex = remainderIndexMap.get(remainder);
            fractionPart.insert(insertIndex,'(').append(')');
        }
        //拼接
        sb.append(fractionPart);
        return sb.toString();
    }
}

 
138. 随机链表的复制(中等)

给你一个长度为 n 的链表,每个节点包含一个额外增加的随机指针 random ,该指针可以指向链表中的任何节点或空节点。

构造这个链表的 深拷贝。 深拷贝应该正好由 n 个 全新 节点组成,其中每个新节点的值都设为其对应的原节点的值。新节点的 next 指针和 random 指针也都应指向复制链表中的新节点,并使原链表和复制链表中的这些指针能够表示相同的链表状态。复制链表中的指针都不应指向原链表中的节点 

例如,如果原链表中有 X 和 Y 两个节点,其中 X.random --> Y 。那么在复制链表中对应的两个节点 x 和 y ,同样有 x.random --> y 。

返回复制链表的头节点。

用一个由 n 个节点组成的链表来表示输入/输出中的链表。每个节点用一个 [val, random_index] 表示:

  • val:一个表示 Node.val 的整数。
  • random_index:随机指针指向的节点索引(范围从 0 到 n-1);如果不指向任何节点,则为  null 。

你的代码  接受原链表的头节点 head 作为传入参数。

解法一、哈希

第一个循环不考虑random指针,先建立同值节点的一对一映射。第二个循环才开始考虑random,把random复制和匹配。

class Solution {
    public static Node copyRandomList(Node head) {
        if(head == null)return null;
        Map<Node,Node> hs = new HashMap<>();
        Node newHead = new Node(head.val);
        Node newTemp = newHead;
        hs.put(head,newHead);
        hs.put(null,null);
        for(Node i = head.next;i != null;i = i.next){
            newTemp.next = new Node(i.val);
            newTemp = newTemp.next;
            hs.put(i,newTemp);
        }
        newTemp = newHead;
        for(Node i = head;i != null;i = i.next){
            newTemp.random = hs.get(i.random);
            newTemp = newTemp.next;
        }
        return newHead;
    }
}

 

解法二、哈希+递归

哈希的格式和解法一一样,依旧是已有链表为键,复制链表为值

class Solution {
    Map<Node, Node> cachedNode = new HashMap<Node, Node>();

    public Node copyRandomList(Node head) {
//如果是null,返回null
        if (head == null) {
            return null;
        }
//如果不存在这个映射,添加这个映射
        if (!cachedNode.containsKey(head)) {
            Node headNew = new Node(head.val);
            cachedNode.put(head, headNew);
            headNew.next = copyRandomList(head.next);
            headNew.random = copyRandomList(head.random);
        }
        return cachedNode.get(head);
    }
}

作者:力扣官方题解
链接:https://leetcode.cn/problems/copy-list-with-random-pointer/solutions/889166/fu-zhi-dai-sui-ji-zhi-zhen-de-lian-biao-rblsf/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

 解法三、后继复制

不用哈希。这道题里哈希的本质是给原node返回新node,可以用后继的形式和链表的特性,让原node.next=新node.next,即A→B→C 变成A→A*→B→B*→C→C*。注意,对于null要特别处理

class Solution {
    public Node copyRandomList(Node head) {
        if (head == null) {
            return null;
        }
        for (Node node = head; node != null; node = node.next.next) {
            Node nodeNew = new Node(node.val);
            nodeNew.next = node.next;
            node.next = nodeNew;
        }
        for (Node node = head; node != null; node = node.next.next) {
            Node nodeNew = node.next;
            nodeNew.random = (node.random != null) ? node.random.next : null;
        }
        Node headNew = head.next;
        for (Node node = head; node != null; node = node.next) {
            Node nodeNew = node.next;
            node.next = node.next.next;
            nodeNew.next = (nodeNew.next != null) ? nodeNew.next.next : null;
        }
        return headNew;
    }
}

作者:力扣官方题解
链接:https://leetcode.cn/problems/copy-list-with-random-pointer/solutions/889166/fu-zhi-dai-sui-ji-zhi-zhen-de-lian-biao-rblsf/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

1. 两数之和(简单)

给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target  的那 两个 整数,并返回它们的数组下标。

你可以假设每种输入只会对应一个答案,并且你不能使用两次相同的元素。

你可以按任意顺序返回答案。

解法一、哈希表 

如果有target-nums[i],就作为键提出值(下标),和现下标一起塞进res数组返回

class Solution {
    public int[] twoSum(int[] nums, int target) {
        HashMap<Integer,Integer> hm = new HashMap<>();
        int[] res = new int[2];
        for(int i = 0;i < nums.length;i++){
            if(hm.containsKey(target - nums[i])){
                res[0] = hm.get(target-nums[i]);
                res[1] = i;
                return res;
            }else{
                hm.put(nums[i],i);
            }
        }
        return res;
    }
}

167. 两数之和 II - 输入有序数组(中等)

给你一个下标从 1 开始的整数数组 numbers ,该数组已按 非递减顺序排列  ,请你从数组中找出满足相加之和等于目标数 target 的两个数。如果设这两个数分别是 numbers[index1] 和 numbers[index2] ,则 1 <= index1 < index2 <= numbers.length 。

以长度为 2 的整数数组 [index1, index2] 的形式返回这两个整数的下标 index1  index2

你可以假设每个输入 只对应唯一的答案 ,而且你 不可以 重复使用相同的元素。

你所设计的解决方案必须只使用常量级的额外空间。

 解法一、双指针

中途更新了两次,一次是没有考虑到那个cur=numbers.length的条件,一次是如果不加那个do-while循环,在一个很长且重复的数列里会超时。如:{-1,-1,-1····-1,1,1}对target 2。所以采用do-while循环,跳过所有重复的pre部分。

如果相等,则返回;如果pre + cur > target 或者cur走到结尾(在正常的循环里,这种情况一定是pre + cur < target),则pre增大,cur减小到pre的后一位;若pre+cur<target,cur增大。

class Solution {
    public int[] twoSum(int[] numbers, int target) {
        int pre = 0,cur = 1;
        int[] res = new int[2];
        while(pre < numbers.length){
            if(numbers[pre] + numbers[cur] == target){
                res[0] = pre+1;
                res[1] = cur+1;
                return res;
            }else if(numbers[pre] + numbers[cur] > target || cur == numbers.length - 1){
                do{
                    pre++;
                }while(pre < numbers.length && numbers[pre] == numbers[pre-1]);
                cur = pre+1;
            }else{//<的情况
                cur++;
            }
        }
        return res;
    }
}

 

解法二、二分查找

固定一找二。

class Solution {
    public int[] twoSum(int[] numbers, int target) {
        for (int i = 0; i < numbers.length; ++i) {
            int low = i + 1, high = numbers.length - 1;
            while (low <= high) {
                int mid = (high - low) / 2 + low;
                if (numbers[mid] == target - numbers[i]) {
                    return new int[]{i + 1, mid + 1};
                } else if (numbers[mid] > target - numbers[i]) {
                    high = mid - 1;
                } else {
                    low = mid + 1;
                }
            }
        }
        return new int[]{-1, -1};
    }
}

作者:力扣官方题解
链接:https://leetcode.cn/problems/two-sum-ii-input-array-is-sorted/solutions/337156/liang-shu-zhi-he-ii-shu-ru-you-xu-shu-zu-by-leet-2/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

解法三、双指针优化

我做的思路太复杂了。其实只要一个指头、一个指尾即可。

class Solution {
    public int[] twoSum(int[] numbers, int target) {
        int low = 0, high = numbers.length - 1;
        while (low < high) {
            int sum = numbers[low] + numbers[high];
            if (sum == target) {
                return new int[]{low + 1, high + 1};
            } else if (sum < target) {
                ++low;
            } else {
                --high;
            }
        }
        return new int[]{-1, -1};
    }
}

作者:力扣官方题解
链接:https://leetcode.cn/problems/two-sum-ii-input-array-is-sorted/solutions/337156/liang-shu-zhi-he-ii-shu-ru-you-xu-shu-zu-by-leet-2/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

599. 两个列表的最小索引总和(简单)

假设 Andy 和 Doris 想在晚餐时选择一家餐厅,并且他们都有一个表示最喜爱餐厅的列表,每个餐厅的名字用字符串表示。

你需要帮助他们用最少的索引和找出他们共同喜爱的餐厅。 如果答案不止一个,则输出所有答案并且不考虑顺序。 你可以假设答案总是存在。

解法一、哈希

第一个双重for循环,若是一致,则放入哈希表,并且更新最小索引和。第二个for循环,若是符合最小索引和,放进list

能想到很耗时,但没想到这么耗。。

class Solution {
    public String[] findRestaurant(String[] list1, String[] list2) {
        int min = 2000;
        HashMap<Integer,Integer> hm = new HashMap<>();
        List<String> res= new LinkedList<>();
        for(int i = 0;i < list1.length;i++){
            for(int j = 0;j < list2.length;j++){
                if(list1[i].equals(list2[j])){
                    hm.put(i,j);
                    min = Math.min(min,i+j);
                }
            }
        }
        for(int i : hm.keySet()){
            if(i + hm.get(i) == min){
                res.add(list1[i]);
            }
        }
        return res.toArray(new String[0]);
    }
}

 

改进版本。这里先把所有list1放进去了,避开了n^2的双循环,对于list2,只需要O(1)地查找就好。并且,第二次循环寻找餐厅的时候,如果当前索引已经超过了最小索引和,则可以直接退出循环。

class Solution {
    public String[] findRestaurant(String[] list1, String[] list2) {
        Map<String, Integer> index = new HashMap<String, Integer>();
        for (int i = 0; i < list1.length; i++) {
            index.put(list1[i], i);
        }

        List<String> ret = new ArrayList<String>();
        int indexSum = Integer.MAX_VALUE;
        for (int i = 0; i < list2.length; i++) {
            if (index.containsKey(list2[i])) {
                int j = index.get(list2[i]);
                if (i + j < indexSum) {
                    ret.clear();
                    ret.add(list2[i]);
                    indexSum = i + j;
                } else if (i + j == indexSum) {
                    ret.add(list2[i]);
                }
            }
        }
        return ret.toArray(new String[ret.size()]);
    }
}

作者:力扣官方题解
链接:https://leetcode.cn/problems/minimum-index-sum-of-two-lists/solutions/1330945/liang-ge-lie-biao-de-zui-xiao-suo-yin-zo-5m9w/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

219. 存在重复元素 II(简单) 

给你一个整数数组 nums 和一个整数 k ,判断数组中是否存在两个 不同的索引 i 和 j ,满足 nums[i] == nums[j] 且 abs(i - j) <= k 。如果存在,返回 true ;否则,返回 false 。

解法一、哈希表

根据数字取下标。如果已经有这个键,则判断距离是否合适。然后,对于这个值,添加/更新哈希集。

class Solution {
    public boolean containsNearbyDuplicate(int[] nums, int k) {
        HashMap<Integer,Integer> hm = new HashMap<>();
        for(int i = 0;i < nums.length;i++){
            if(hm.containsKey(nums[i]) && i - hm.get(nums[i]) <= k) {
                return true;
            }
            hm.put(nums[i],i);
        }
        return false;
    }
}

 

解法二、滑动窗口

感觉还是挺有意思的···对于i,如果这个数字已经存在,则直接返回true。如果不存在,则加入。若set内数量大于k,删掉下标最小的(i-k-1)。这题能够用滑动窗口,因为涉及下标(则不能排序)又不涉及具体下标,而是下标间的相对关系(则不必一定使用哈希表存储数字-下标)

这里一定要注意一个细节。对于大于k的i,它在比较时,是还没有加入set的。所以这里是i>k时踢掉i-k-1,而不是i>=k时踢掉i-k。还有一点是这里的add在重复时会直接返回false,所以第二个if达成代表set里已有这个数字。

class Solution {
    public boolean containsNearbyDuplicate(int[] nums, int k) {
        Set<Integer> set = new HashSet<Integer>();
        int length = nums.length;
        for (int i = 0; i < length; i++) {
            if (i > k) {
                set.remove(nums[i - k - 1]);
            }
            if (!set.add(nums[i])) {
                return true;
            }
        }
        return false;
    }
}

作者:力扣官方题解
链接:https://leetcode.cn/problems/contains-duplicate-ii/solutions/1218075/cun-zai-zhong-fu-yuan-su-ii-by-leetcode-kluvk/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

 
220. 存在重复元素 III(困难)

给你一个整数数组 nums 和两个整数 indexDiff 和 valueDiff 。

找出满足下述条件的下标对 (i, j)

  • i != j,
  • abs(i - j) <= indexDiff
  • abs(nums[i] - nums[j]) <= valueDiff

如果存在,返回 true ;否则,返回 false 

解法一、滑动窗口

模拟了219的做法。这道题使用了双重区间,即下标区间和值区间,下标区间依旧可以通过219的滑动区间解决限制,但对于值区间,比起在值区间内进行值遍历,我首先想到的办法是先找到最接近的值、再判断是否在区间。(官方用的是前者,也就是一个)

在这里,对abs(取绝对值)的方法做了一个转换,使用了TreeSet.higher函数(寻找集里严格大于e的最小的那个数),而把e定为num-valueDiff-1。这样,就可以把比原来那个数小、但符合区间的数找到,而不必再使用一次lower函数了。不过,ceiling是大于等于,也就是加上原本那个数的higher。

class Solution {
    public boolean containsNearbyAlmostDuplicate(int[] nums, int indexDiff, int valueDiff) {
        TreeSet<Integer> ts = new TreeSet<>();
        for(int i = 0;i < nums.length;i++){
            if(i > indexDiff){
                ts.remove(nums[i-indexDiff-1]);
            }
            if(ts.higher(nums[i] - valueDiff - 1)!= null && ts.higher(nums[i] - valueDiff - 1) <= nums[i] + valueDiff){
                return true;
            }
            ts.add(nums[i]);
        }
        return false;
    }
}

 

 解法二、桶排序(哈希)

因为最差值的是t,也就是说,x和x+t是最极端的符合条件的情况,它们需要在一个桶里。所以,一个桶的大小是t+1。同219,这里的格式也是大体来说①判断②踢出多余的③最后再放入i

同一个桶必然符合,不同的桶必然不符合。如果在相邻的桶,取出来再试试。

对于负数的情况。假设t是5,而3和-3显然都会被分到0组,但它们差6。所以需要对负数做一个拆分,即x+1/(t+1) - 1,确保分到不同的组。即,原来-4到-1也在0组,这样的话,就可以让-5到-1分到-1组,实现错位。这样的技巧在计算数根中也有体现,即要判断一个数字取数根后的范围在1-9内,是先减再加。

a的数根b = (a-1) % 9+1, 即 mod(a-1, 9)+1,且a ∈ N*

class Solution {
    public boolean containsNearbyAlmostDuplicate(int[] nums, int k, int t) {
        int n = nums.length;
        Map<Long, Long> map = new HashMap<Long, Long>();
        long w = (long) t + 1;
        for (int i = 0; i < n; i++) {
            long id = getID(nums[i], w);
            if (map.containsKey(id)) {
                return true;
            }
            if (map.containsKey(id - 1) && Math.abs(nums[i] - map.get(id - 1)) < w) {
                return true;
            }
            if (map.containsKey(id + 1) && Math.abs(nums[i] - map.get(id + 1)) < w) {
                return true;
            }
            map.put(id, (long) nums[i]);
            if (i >= k) {
                map.remove(getID(nums[i - k], w));
            }
        }
        return false;
    }

    public long getID(long x, long w) {
        if (x >= 0) {
            return x / w;
        }
        return (x + 1) / w - 1;
    }
}

作者:力扣官方题解
链接:https://leetcode.cn/problems/contains-duplicate-iii/solutions/726619/cun-zai-zhong-fu-yuan-su-iii-by-leetcode-bbkt/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

 


碎碎念 

  • 这次记熟了边界讨论,在数组里用索引前,先判断索引在不在范围。
  • 普通的链表题只要①清楚每一个通路②舍得用变量 总能做出来
  • 试出来了list转array的方法。。括号里要返回一个带类型的值
  • 对于哈希表,如果重复key,则会覆盖;如果想要去除重复value,有三种办法:①新建hashmap,借助containsValue函数。②利用哈希集(HashSet)的去重特性。③利用stream(方法来源见引用条)

如何删除 HashMap 中的重复元素?—— 99% 的人不知道的第 3 种实现思路_hashmap去重-CSDN博客

  • 10
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值