文章目录
前言
做leetcode题目算法总结
来源:力扣(LeetCode)
链接:https://leetcode-cn.com
刷题顺序转载自作者:noone_
链接:https://leetcode-cn.com/circle/article/48kq9d/
一、数组
1.1 数组的遍历
1.2 统计数组中的元素
1.2.0 方法总结
空间换时间
方法一:哈希表
哈希表是一个可以支持快速查找的数据结构:给定一个元素,我们可以在 O(1) 的时间查找该元素是否在哈希表中。
我们可以将数组所有的数放入哈希表,随后从 11 开始依次枚举正整数,并判断其是否在哈希表中;
方法二:对应数组索引标记(对整数数据有效)
将遍历的数据作为索引将新的数组上的数据作为标记,有数据就有该数。
该方法与用TreeMap保存数据原理类似。用索引的性质排序。
时间换空间
方法一 索引标记数据
将当前数组中的数与数组索引做映射,将遍历的数据作为索引标记当前数组上的数。(常用取负数。)
如果当前数组遍历的数x为整数,则数组的第x个数标记为负数,表示索引对应的数存在。
方法二 置换
是数据通过交换放置到索引对应的位置上。
如果数组中包含x∈[1,N],那么恢复后,数组的第 x−1 个元素为 x。
1.2.3 找到所有数组中消失的数字448
题目描述:
给定一个范围在 1 ≤ a[i] ≤ n ( n = 数组大小 ) 的 整型数组,数组中的元素一些出现了两次,另一些只出现一次。找到所有在 [1, n] 范围之间没有出现在数组中的数字。
您能在不使用额外空间且时间复杂度为O(n)的情况下完成这个任务吗? 你可以假定返回的数组不算在额外空间内。
示例:
输入: [4,3,2,7,8,2,3,1]
输出: [5,6]
解题思路:
方法一:使用哈希表
我们假设数组大小为 N,它应该包含从 1 到 N 的数字。但是有些数字丢失了,我们要做的是记录我们在数组中遇到的数字。然后从 1⋯N 检查哈希表中没有出现的数字。
通过map的key-value形式可以快速的查到该key对应的value是否有值。
从1⋯N作为key值检查是否有value,可以是在检查是否有该key。 如果用list来做效率较低。
class Solution {
public List<Integer> findDisappearedNumbers(int[] nums) {
HashMap<Integer, Boolean> hashTable = new HashMap<Integer, Boolean>();
for (int i = 0; i < nums.length; i++) {
hashTable.put(nums[i], true);
}
List<Integer> result = new LinkedList<Integer>();
for (int i = 1; i <= nums.length; i++) {
if (!hashTable.containsKey(i)) {
result.add(i);
}
}
return result;
}
}
方法二:原地修改
将数据与位置索引做一个映射,将二者联系起来。这里是通过将数据值作为索引,将其位索引的数变为负数来标记该数据存在。
示例:
- 原始数组:[4,3,2,7,8,2,3,1
- 重置后为:[-4,-3,-2,-7,8,2,-3,-1]
class Solution {
public List<Integer> findDisappearedNumbers(int[] nums) {
LinkedList list = new LinkedList();
for(int num : nums){
int index = Math.abs(num)-1;
if( nums[index] > 0 ){
nums[index] = -1*nums[index];
}
}
for(int i = 0;i < nums.length;i++){
if(nums[i] > 0){
list.add(i+1);
}
}
return list;
}
}
1.2.4 数组中重复的数据442
题目描述:
给定一个整数数组 a,其中1 ≤ a[i] ≤ n (n为数组长度), 其中有些元素出现两次而其他元素出现一次。
找到所有出现两次的元素。
你可以不用到任何额外空间并在O(n)时间复杂度内解决这个问题吗?
示例:
输入: [4,3,2,7,8,2,3,1]
输出: [2,3]
解题思路:
方法一:使用哈希表
将所有遍历的数据存放到map中,key保存出现的数据;value保存出现的次数。
通过entrySet()遍历key-value。查找个数为2的key值。
class Solution {
public List<Integer> findDuplicates(int[] nums) {
List list = new ArrayList();
HashMap hashMap = new HashMap<Integer, Integer>();
for(int num : nums){
if(hashMap.get(num) == null) {
hashMap.put(num,1);
}else{
hashMap.put(num,(int)hashMap.get(num)+1);
}
}
Iterator<Map.Entry<Integer, Integer>> iterator = hashMap.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry<Integer,Integer> entry = (Map.Entry<Integer,Integer>) iterator.next();
if(entry.getValue() == 2){
list.add(entry.getKey());
}
}
return list;
}
}
方法二:原地修改
在输入数组与索引中做映射关系,类似448.
在输入数组中用数字的正负来表示该位置所对应数字是否已经出现过。遍历输入数组,给对应位置的数字取相反数,如果已经是负数,说明前面已经出现过,直接放入输出列表。
class Solution {
public List<Integer> findDuplicates(int[] nums) {
List list = new ArrayList();
for(int num : nums){
int index = Math.abs(num)-1;
if( nums[index] > 0 ){
nums[index] = -1*nums[index];
}else{
list.add(index+1);
}
}
return list;
}
}
1.2.5 缺失的第一个正数41
题目描述:
给你一个未排序的整数数组,请你找出其中没有出现的最小的正整数。
示例:
输入: [3,4,-1,1]
输出: 2
解题思路:
如果本题没有额外的时空复杂度要求,那么就很容易实现:
我们可以将数组所有的数放入哈希表,随后从 1 开始依次枚举正整数,并判断其是否在哈希表中;
我们可以从 1 开始依次枚举正整数,并遍历数组,判断其是否在数组中
方法一:使用HashMap的key 标记已经出现过的数据。
遍历数组中,将大于0 并小于数组长度+2的数放入map的key中,从小到大查找是否有该key值。(用TreeMap性能也不好)
(原本以为map能够提升性能,实际上还是复杂度过高)
class Solution {
public int firstMissingPositive(int[] nums) {
TreeMap<Integer, Integer> treeMap = new TreeMap<>();
for(int num : nums){
if(num > 0 && num < nums.length+2){
treeMap.put(num,num);
}
}
for(int i = 1;i <= nums.length+1;i++){
if(treeMap.get(i) == null){
return i;
}
}
return 1;
}
}
方法二:使用新数组索引标记出现过的数
(时间消耗降低空间消耗提升)思路与上方法一致 遍历数组中,将大于0并小于数组长度+2的数作为res数组的索引将其位置上的值设置为1,从1到大查找是索引位置上数据不为1的值。
时间复杂度:O(N),这里 N 表示数组的长度。第 1 次遍历了数组,第 2 次遍历了区间 [1, res] 里的元素。
空间复杂度:O(N),把 N个数存在哈希表里面,使用了 N 个空间
class Solution {
public int firstMissingPositive(int[] nums) {
int[] res = new int[nums.length+2];
for(int num : nums){
if(num > 0 && num < nums.length+2){
res[num] = 1;
}
}
for(int i = 1;i < res.length;i++){
if(res[i] != 1){
return i;
}
}
return 1;
}
}
方法三:原地标记法
对于遍历到的数 ,如果它在 [1, N]的范围内,那么就将数组中的对应位置打上「标记」。「标记」表示为「负号」。 把不在 [1,N]范围内的数修改成任意一个大于 N 的数(例如 N+1)。
class Solution {
public int firstMissingPositive(int[] nums) {
int n = nums.length;
for (int i = 0; i < n; ++i) {
if (nums[i] <= 0) {
nums[i] = n + 1;
}
}
for (int i = 0; i < n; ++i) {
int num = Math.abs(nums[i]);
if (num <= n) {
nums[num - 1] = -Math.abs(nums[num - 1]);
}
}
for (int i = 0; i < n; ++i) {
if (nums[i] > 0) {
return i + 1;
}
}
return n + 1;
}
}
方法四:原地置换
如果数组中包含 x∈[1,N],那么恢复后,数组的第 x−1 个元素为 x。
class Solution {
public int firstMissingPositive(int[] nums) {
int n = nums.length;
for (int i = 0; i < n; ++i) {
while (nums[i] > 0 && nums[i] <= n && nums[nums[i] - 1] != nums[i]) {
int temp = nums[nums[i] - 1];
nums[nums[i] - 1] = nums[i];
nums[i] = temp;
}
}
for (int i = 0; i < n; ++i) {
if (nums[i] != i + 1) {
return i + 1;
}
}
return n + 1;
}
}
1.3 数组的改变、移动
1.3.0 方法总结
1.3.1 非递减数列665
题目描述:
给你一个长度为 n 的整数数组,请你判断在 最多 改变 1 个元素的情况下,该数组能否变成一个非递减数列。
我们是这样定义一个非递减数列的: 对于数组中所有的 i (0 <= i <= n-2),总满足 nums[i] <= nums[i + 1]。
示例:
输入: nums = [4,2,1] 输出: false 解释: 你不能在只改变一个元素的情况下将其变为非递减数列。
解题思路:
按照要求看是否能实现。
两次遍历,第一次找到需要改的数据并改正。记录修改次数。 第二次重新查询是否还存在数据使其不为递减数列
方法一:暴力求解方法
class Solution {
public boolean checkPossibility(int[] nums) {
int res = 0;
for(int i = 0;i < nums.length-2;i++){
if(nums[i] > nums[i+1] && nums[i] > nums[i+2]){
res++;nums[i] = nums[i+1];
break;
}
}
for(int i = 0;i < nums.length-1;i++){
if(nums[i] > nums[i+1]){
res++;
if(res>=2){
return false;
}
}
}
return true;
}
}
1.3.3移动零283
题目描述:
给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。
- 必须在原数组上操作,不能拷贝额外的数组。
- 尽量减少操作次数。
示例:
输入: [0,1,0,3,12]
输出: [1,3,12,0,0]
解题思路:
借鉴现有算法的思想。如借鉴冒泡和快速排序的思路。
方法一:冒泡
class Solution {
public void moveZeroes(int[] nums) {
int length = nums.length;
for(int i = 1;i < length;i++){
for(int j = 0;j < length - i;j++){
if(nums[j] == 0){
nums[j] = nums[j+1];
nums[j+1] = 0;
}
}
}
}
}
方法二:快速排序
这里参考了快速排序的思想,快速排序首先要确定一个待分割的元素做中间点x,然后把所有小于等于x的元素放到x的左边,大于x的元素放到其右边。
这里我们可以用0当做这个中间点,把不等于0(注意题目没说不能有负数)的放到中间点的左边,等于0的放到其右边。
class Solution {
public void moveZeroes(int[] nums) {
if(nums==null) {
return;
}
//两个指针i和j
int j = 0;
for(int i=0;i<nums.length;i++) {
//当前元素!=0,就把其交换到左边,等于0的交换到右边
if(nums[i]!=0) {
int tmp = nums[i];
nums[i] = nums[j];
nums[j++] = tmp;
}
}
}
}
作者:wang_ni_ma
链接:https://leetcode-cn.com/problems/move-zeroes/solution/dong-hua-yan-shi-283yi-dong-ling-by-wang_ni_ma/
1.9 前缀和数组
238. 除自身以外数组的乘积
题目描述:
给你一个长度为 n 的整数数组 nums,其中 n > 1,返回输出数组 output ,其中 output[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积。
示例:
输入: [1,2,3,4]
输出: [24,12,8,6]
提示:题目数据保证数组之中任意元素的全部前缀元素和后缀(甚至是整个数组)的乘积都在 32 位整数范围内。
说明: 请不要使用除法,且在 O(n) 时间复杂度内完成此题。
进阶:
你可以在常数空间复杂度内完成这个题目吗?( 出于对空间复杂度分析的目的,输出数组不被视为额外空间。)
解题思路:
我们不必将所有数字的乘积除以给定索引处的数字得到相应的答案,而是利用索引左侧所有数字的乘积和右侧所有数字的乘积(即前缀与后缀)相乘得到答案。
对于给定索引 ii,我们将使用它左边所有数字的乘积乘以右边所有数字的乘积。下面让我们更加具体的描述这个算法。
乘积 = 当前数左边的乘积 * 当前数右边的乘积
方法一:使用前缀后缀来计算。
class Solution {
public int[] productExceptSelf(int[] nums) {
int n = nums.length,ans = 1;
int[] output = new int[n];
//Arrays.fill(output,1);
//前缀的乘积
output[0] = 1;
for(int i = 1;i < n;i++){
output[i] = output[i-1]*nums[i-1];
}
//用ans表示后缀的乘积值
for(int i = n-2;i >= 0;i--) {
ans =ans*nums[i+1];
output[i] = output[i]*ans;
}
return output;
}
}
二、字符串
2.5 字符的统计
49. 字母异位词分组
题目描述:
给定一个字符串数组,将字母异位词组合在一起。字母异位词指字母相同,但排列不同的字符串。
示例:
输入: [“eat”, “tea”, “tan”, “ate”, “nat”, “bat”]
输出:
[ [“ate”,“eat”,“tea”],
[“nat”,“tan”],
[“bat”] ]
解题思路:
分组的思路:就是找同一组内唯一的key值,用HashMap保存的value保存数据。
方法一:排序
由于互为字母异位词的两个字符串包含的字母相同,因此对两个字符串分别进行排序之后得到的字符串一定是相同的,故可以将排序之后的字符串作为哈希表的键。
class Solution {
public List<List<String>> groupAnagrams(String[] strs) {
HashMap<String, List<String>> map = new HashMap<>();
String key;
for(String str : strs){
key = stringValue(str);
//获取对应key值的map的value。
List<String> list = map.getOrDefault(key, new ArrayList<String>());
//将value修改。
list.add(str);
//重新放入。
map.put(key, list);
}
return new ArrayList<List<String>>(map.values());
}
//字母异位词的key值
private String stringValue(String str1){
char[] array1 = str1.toCharArray();
Arrays.sort(array1);
String key1 = new String(array1);
return key1;
}
}
一级标题
二级标题
三级标题
题目描述:
示例:
解题思路:
方法一:使用哈希表