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(方法来源见引用条)