442. 数组中重复的数据
2.分析
这一题刚刚开始是很非常懵,主要是没有理解到"给定一个整数数组 a,其中1 ≤ a[i] ≤ n (n为数组长度)" 这个条件的真谛。 这个条件就是暗示我们可以使用这个数组里面的值帮我们来记录一些信息。
1.首先你的约束条件是 “你可以不用到任何额外空间并在O(n)时间复杂度内” ,所以不能两次遍历,只能遍历一次且需要用到数组里面的位置来帮助我们记录信息。
2.那么我们就考虑循环每个值,利用这个(值-1)代表的索引值来记录是否已经被遍历到。
3.遍历到则变为负数,如果第二次再次遍历到这个数,会发现这个数为负,则说明这个数是重复的。
class Solution(object):
def findDuplicates(self, nums):
"""
主要的思路就是利用正负来保存第一次遍历到的数值的状态
:type nums: List[int]
:rtype: List[int]
"""
res = []
for i in range(len(nums)):
index = nums[i] - 1 # 取到以这个值-1所在索引的值,判断这个值是否大于0
if nums[index] > 0: # 如果大于0,则说明这个值是第一次被遍历到,
nums[index] = -nums[index] # 则将这个值-1所在索引的值变负数,通过这个来记住已经遍历过一遍了
else: # 当这个值是负数,表示这个值第二次被遍历到了,所以它是重复的,故添加到res中即可
res.append(nums[i])
return res
2、思路
采用桶排序的思路,只不过桶排序需要开辟新数组,而题目中的原数组就是长度为 n,且数组中数字是 1-n,说明可以将原数组原地桶排序。
采用遍历两边的思路,第一遍将 nums[i] != nums[nums[i] - 1] 的数交换;第二遍如果 i != nums[i] - 1 则说明它无处安放,直接选它即可。如果是一遍的话,那交换过程中可能会选到重复的,只能修改原来数字或者用 set,不推荐。
class Solution {
public List<Integer> findDuplicates(int[] nums) {
if(nums == null || nums.length == 0){
return new ArrayList<>();
}
List<Integer> list = new ArrayList<>();
for(int i = 0; i < nums.length; i++){
while(nums[i] != nums[nums[i] - 1]){
swap(nums, i, nums[i] - 1);
}
// // 这种会出现重复的,所以要在外边,要不然只能用 set
// if(i != nums[i] - 1 && nums[i] == nums[nums[i] - 1]){
// list.add(nums[i]);
// }
}
for(int i = 0; i < nums.length; i++){
if(i != nums[i] - 1 && nums[i] == nums[nums[i] - 1]){
list.add(nums[i]);
}
}
return list;
}
private void swap(int[] nums, int i, int j){
int temp = nums[i];
nums[i] = nums[j];
nums[j] = temp;
}
}
class Solution(object):
def findDuplicates(self, nums):
"""
:type nums: List[int]
:rtype: List[int]
"""
dic = collections.Counter(nums)
res = []
for key, value in dic.items():
if value == 2:
res.append(key)
return res
10. 正则表达式匹配
329. 矩阵中的最长递增路径
347. 前 K 个高频元素
给你一个整数数组 nums 和一个整数 k ,请你返回其中出现频率前 k 高的元素。你可以按 任意顺序 返回答案。
示例:
输入: nums = [1,1,1,2,2,3], k = 2
输出: [1,2]
思路:
桶排序
先建立一个字典来统计数组中每个数出现的次数,然后建立一个二维数组,这个二维数组的索引表示每个桶的序号,初始化每个桶是一个空列表,然后读取字典中的键值对,让值也就是出现的频率作为二维数组的索引,将键也就是数放进对应的桶里,这样出现频率相同的数就放进了同一个桶里,并且出现频率就是对应的索引,即出现频率是由小到大排列的。
将桶整体逆序,然后来找前K个元素,当桶非空时,并且K大于0的情况下,开始往结果里加元素,直到当前桶内元素为空的时候,才有可能遍历下一个桶,前提是K大于0,每在结果里加一个元素,k减1,对结果中的元素计数,直到k等于0直接返回结果,得到频率前K高的元素数组。
class Solution(object):
def topKFrequent(self, nums, k):
"""
:type nums: List[int]
:type k: int
:rtype: List[int]
"""
dict1 = {}
res = []
for i in range(len(nums)):
if nums[i] not in dict1:
dict1[nums[i]] = 1
else:
dict1[nums[i]] += 1
list1 = [[] for _ in range(len(nums) + 1)]
for key in dict1:
index = dict1[key]
list1[index].append(key)
list1.reverse()
for item in list1:
if item and k > 0:
while item and k > 0:
res.append(item.pop())
k -= 1
if k == 0:
return res
return res
class Solution(object):
def topKFrequent(self, nums, k):
"""
:type nums: List[int]
:type k: int
:rtype: List[int]
"""
result = []
d = collections.Counter(nums).most_common(k)
for e in d:
result.append(e[0])
return result
287. 寻找重复数
class Solution(object):
def findDuplicate(self, nums):
if not nums:
return None
nums.sort()
for i in range(1,len(nums)):
if nums[i] == nums[i-1]:
return nums[i]
break
思路二:借鉴链表快慢指针判断有环丙并寻找入口节点的思路
使用环形链表II的方法解题(142.环形链表II),使用 142 题的思想来解决此题的关键是要理解如何将输入的数组看作为链表。
首先明确前提,整数的数组 nums 中的数字范围是 [1,n]。考虑一下两种情况:
如果数组中没有重复的数,以数组 [1,3,4,2]为例,我们将数组下标 n 和数 nums[n] 建立一个映射关系 f(n),
其映射关系 n->f(n)为:
0->1
1->3
2->4
3->2
我们从下标为 0 出发,根据 f(n) 计算出一个值,以这个值为新的下标,再用这个函数计算,以此类推,直到下标超界。这样可以产生一个类似链表一样的序列。
0->1->3->2->4->null
如果数组中有重复的数,以数组 [1,3,4,2,2] 为例,我们将数组下标 n 和数 nums[n] 建立一个映射关系 f(n),
其映射关系 n->f(n) 为:
0->1
1->3
2->4
3->2
4->2
同样的,我们从下标为 0 出发,根据 f(n) 计算出一个值,以这个值为新的下标,再用这个函数计算,以此类推产生一个类似链表一样的序列。
0->1->3->2->4->2->4->2->……
从理论上讲,数组中如果有重复的数,那么就会产生多对一的映射,这样,形成的链表就一定会有环路了,
综上
1.数组中有一个重复的整数 <> 链表中存在环
2.找到数组中的重复整数 <> 找到链表的环入口
至此,问题转换为 142 题。那么针对此题,快、慢指针该如何走呢。根据上述数组转链表的映射关系,可推出
142 题中慢指针走一步 slow = slow.next ==> 本题 slow = nums[slow]
142 题中快指针走两步 fast = fast.next.next ==> 本题 fast = nums[nums[fast]]
class Solution:
def findDuplicate(self, nums: List[int]) -> int:
slow, fast = 0, 0
while True:
fast = nums[nums[fast]]
slow = nums[slow]
# 相遇说明有环,快指针回头结点
if fast == slow:
fast = 0
while fast != slow:
slow = nums[slow]
fast = nums[fast]
return slow
思路三:不符合原题不准修改nums数组的要求
由于是1-n个数字排在n+1个位置上,那么肯定有一位数字与其对应下标的数字是重复的,把首位作为该位,当不相等的时候,就一直将nums[0]和nums[nums[0]]进行交换,直到相等,直接返回nums[0]
class Solution(object):
def findDuplicate(self, nums):
if not nums:
return None
for i in range(len(nums)):
if nums[0] == nums[nums[0]]:
return nums[0]
else:
tmp = nums[nums[0]]
nums[nums[0]] = nums[0]
nums[0] = tmp
887. 鸡蛋掉落
189. 轮转数组
给你一个数组,将数组中的元素向右轮转 k 个位置,其中 k 是非负数。
示例 1:
输入: nums = [1,2,3,4,5,6,7], k = 3
输出: [5,6,7,1,2,3,4]
解释:
向右轮转 1 步: [7,1,2,3,4,5,6]
向右轮转 2 步: [6,7,1,2,3,4,5]
向右轮转 3 步: [5,6,7,1,2,3,4]
示例 2:
输入:nums = [-1,-100,3,99], k = 2
输出:[3,99,-1,-100]
解释:
向右轮转 1 步: [99,-1,-100,3]
向右轮转 2 步: [3,99,-1,-100]
class Solution:
def rotate(self, nums: List[int], k: int) -> None:
"""
Do not return anything, modify nums in-place instead.
"""
n = len(nums)
index = list(range(n))
new_index = [(i + k) % n for i in index]
ans = [0]*n
for i in range(n):
ans[new_index[i]] = nums[i]
nums[:] = copy.deepcopy(ans)
class Solution:
def rotate(self, nums: List[int], k: int) -> None:
"""
Do not return anything, modify nums in-place instead.
"""
n = len(nums)
k = k % n
nums[:] = nums[n - k:] + nums[:n - k]
120. 三角形最小路径和
给定一个三角形,找出自顶向下的最小路径和。每一步只能移动到下一行中相邻的结点上。
相邻的结点 在这里指的是 下标 与 上一层结点下标 相同或者等于 上一层结点下标 + 1 的两个结点。
例如,给定三角形:
[
[2],
[3,4],
[6,5,7],
[4,1,8,3]
]
自顶向下的最小路径和为 11(即,2 + 3 + 5 + 1 = 11)。
解题误区
典型的动态规划问题,没有注意到题目相邻节点的信息。🤔智障的我用集合遍历整了半天,结果死在了这个用例:
输入:[[-1],[2,3],[1,-1,-3]]
输出:-2
预期:-1
题目的意思并不是直接求最小路径,隐含的时求在相邻节点的条件下,求最小路径
[
[2],
[3,4],
[6,5,7],
[4,1,8,3]
]
相邻结点:与(i, j)点相邻的结点为(i + 1, j)和 (i + 1, j + 1)
推倒递推公式为:
f(i, j) = min(f(i + 1, j), f(i + 1, j + 1)) + triangle[i][j]
自下而上DP
以自下而上的角度看,三角形中任意一个位置triangle[i][j],只有两个值能移动到这个这里
分别是triangle[i+1][j+1],以及triangle[i+1][j]
自下而上的思维: 当前triangle[i][j]是由其下面的相邻节点计算而来,即正下和右下元素选最小
定义为[N+1][M+1]只是为了更加方便的去处理边界条件。注意记得根据实际情况去初始化
class Solution {
public int minimumTotal(List<List<Integer>> triangle) {
int n = triangle.size();
int[][] dp = new int[n+1][n+1];
//倒数第二行,倒数第二列
for(int i = n - 1; i >= 0; i--) {
for(int j = triangle.get(i).size() - 1; j >= 0; j--) {
dp[i][j] = Math.min(dp[i+1][j], dp[i+1][j+1]) + triangle.get(i).get(j);
}
}
return dp[0][0];
}
}
400. 第N个数字
在无限的整数序列 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, …中找到第 n 个数字。
注意:
n 是正数且在32为整形范围内 ( n < 231)。
示例 1:
输入:
3
输出:
3
示例 2:
输入:
11
输出:
0
说明:
第11个数字在序列 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, … 里是0,它是10的一部分。
class Solution(object):
def findNthDigit(self, n):
"""
:type n: int
:rtype: int
"""
"""
个位数:1-9,一共9个,共计9个数字
2位数:10-99,一共90个,共计180个数字
3位数:100-999,一共900个,共计270个数字
4位数,1000-9999,一共9000个,共计36000个数字 36000=4*9*10**(4-1)
......
"""
#第一步确定n是在几位数里,第二步是确定在几位数的第几位数字的第几位
#第一步
digit=1#位数
while n>digit*9*10**(digit-1):
n-=digit*9*10**(digit-1)
digit+=1
#第二步
a=int((n-1)/digit)#得到几位数的第几位数字
b=int((n-1)%digit)#得到几位数的第几位数字的第几位
num=10**(digit-1)+a#得到第几位数字是多少
res=list(str(num))[b:b+1]#数字转字符再转列表把第几位数的第几位切出来
return int(''.join(res))#列表转字符再转数字
611. 有效三角形的个数
排序+双指针
算法流程:
数组排序
定义左边界和右边界,固定一条边,注意这里的边是数组最右边的元素,咱们倒序固定
当左边界比右边界小,判断?a[left]+a[right]>a[i]如果满足,那么right–,因为数组从左到右是升序,我减少右边界,相当于a[right]减小
反之,left++
维护的res+=right - left
class Solution {
public:
int triangleNumber(vector<int>& nums) {
int n = nums.size();
int res = 0;
sort(nums.begin(),nums.end());
for(int i = n - 1; i > 1; --i){
int left = 0;
int right = i - 1;
while(left < right){
if(nums[left] + nums[right] > nums[i]&&nums[left]!=0&&nums[right]!=0&&nums[i]!=0){
res+=right-left;
--right;
}
else{
++left;
}
}
}
return res;
}
};