算法训练—leetcode—数组篇(一)

本文涉及的leetcode对应的题

1.反转字符串

编写一个函数,其作用是将输入的字符串反转过来。输入字符串以字符数组 char[] 的形式给出。
不要给另外的数组分配额外的空间,你必须原地修改输入数组、使用 O(1) 的额外空间解决这一问题。
你可以假设数组中的所有字符都是 ASCII 码表中的可打印字符。
示例 1:
输入:[“h”,“e”,“l”,“l”,“o”]
输出:[“o”,“l”,“l”,“e”,“h”]
示例 2:
输入:[“H”,“a”,“n”,“n”,“a”,“h”]
输出:[“h”,“a”,“n”,“n”,“a”,“H”]

1.1.双指针:左右双指针同步收缩

在这里插入图片描述
以上图为例,我们定义两个指针left用来用作向右移动,right用来向左移动,每移动一次,就将字符串数组中的left位置的元素和right位置元素进行互换。
两个元素互换规则:借助第三只手temp,A元素给temp,,B元素给A元素,temp给A元素。
伪代码示意

while ( left< right){
	temp = left的值;
	left = right的值;
	right = temp;
	left++;
	right--;
}

完整代码示意

public void reverseString(char[] s) {
	int left = 0;
	int right = s.length-1;
	while ( left < right ){
		//相同元素不交换
		if (s[left] != s[right]){
			char temp = s[left];
			s[left] = s[right];
			s[right] = temp;
		}
		left ++;
		right --;
	}
}

在这里插入图片描述

2.两数之和

给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标。
你可以假设每种输入只会对应一个答案。但是,数组中同一个元素不能使用两遍。
给定 nums = [2, 7, 11, 15], target = 9
因为 nums[0] + nums[1] = 2 + 7 = 9
所以返回 [0, 1]

2.1.双重for循环进行穷举

解题要素:
1、nums [a] + nums [b] = target;
2、要保证nums [a] 和nums [b]不能为同一个元素即a != b
例如nums = [2, 3], target = 4时,2+2 = 4 ,此时2被重复利用,因此a从0 开始循环,b从 a + 1开始循环,即可保证a != b。
伪代码示意

for(第一个元素){
	for(第二个元素 = 第一个元素+1){
		if( 目标值 == 第一个元素 + 第二个元素 ){
			返回元素下标
		}
	}
}

完整代码示意

public static int[] twoSum1(int[] nums, int target) {
	for (int a = 0; a < nums.length; a++) {
		for (int b = a + 1 ; b < nums.length; b++) {
			if (nums[a] + nums[b] == target) {
				return new int[] {a,b};
			}
		}
	}
	return null;
}

在这里插入图片描述

2.2.Hash优化双重for循环

我们可以通过hash来优化掉一层循环,第一次循环为N,第二次循环为N-1,此时时间复杂度为O(n*n).
我们通过空间换时间的方式,先执行一遍循环,将数组中的元素存入HashMap中,第二次循环,我们就在hashMap中去寻找我们所需要的值,从hashMap中查找key的时间复杂度为O(1),因此使用hashMap优化掉一层循环后,时间复杂度变成O(n)
解题要素:
1、keyA = target - nums[b];
循环一遍数组,将数组中的数值存储到HashMap中,其中key为nums[a],value为a,即key为元素的值,value为元素的索引位置
2、根据题意,同一个元素不能使用两次,在双从for循环中,我们的代码是这样写的,
for(a = 0){ for ( b = a +1){ }},因此避免了ab元素不会是同一个元素,当我们将nums存入map时,则循环数组时,和map进行匹配,可能出现同一个元素被我们使用了两次,即 原本为 a + b = target 变成了 a + a = target。因此我们要保证nums [a] 和nums [b]不能为同一个元素,即map.get(keyb ) != a;
伪代码示意

for(){
	mapB.put(元素值,元素下标)}
for(元素 a){
	元素b = 目标值 - 元素a;
	if(mapB中有元素b 并且 b元素和a元素不相等){
		返回元素下标
	}
}
public int[] twoSum(int[] nums, int target) {
	Map<Integer,Integer> keyMapB = new HashMap<>();
	for (int b = 0; b < nums.length; b++) {
		keyMapB.put(nums[b],b);
	}
	for (int a = 0; a < nums.length; a++) {
		int keyB = target - nums[a];
		if(keyMapB.containsKey(keyB) && keyMapB.get(keyB) != a) {
			return new int[] {a,keyMapB.get(keyB)};
		}
	}
	return null;
}

在这里插入图片描述

对于上述方法中,我们执行了两次for循环,也就是2n,可以一边循环,一边往hashMap中存放值。

public int[] twoSum(int[] nums, int target) {
	Map<Integer, Integer> hashtable = new HashMap<Integer, Integer>();
    for (int i = 0; i < nums.length; ++i) {
        if (hashtable.containsKey(target - nums[i])) {
             return new int[]{hashtable.get(target - nums[i]), i};
         }
         hashtable.put(nums[i], i);
     }
     return new int[0];
 }

3.三数之和

给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有满足条件且不重复的三元组。
注意:答案中不可以包含重复的三元组。
给定数组 nums = [-1, 0, 1, 2, -1, -4],
满足要求的三元组集合为:
[
[-1, 0, 1],
[-1, -1, 2]
]

3.1.三重for循环进行穷举

解题思路
1、在两数之和的基础上,在增加一层for循环,由于题中给出要求,不能存在重复的三元组,我们可以利用hashSet的唯一性,做判断
2、先将数组进行排序,排序之后按照从小到大排列,如果某个时候三数相加之和大于0,则之后的任意数相加都一定大于0。
伪代码示意

排序,从小到大
for(第一个元素   循环条件 = 长度-2){
	if(三数之和大于0){
		结束寻找
	}
	for(第二个元素 = 第一个元素 + 1){
		for(第三个元素 = 第二个元素 +1 ){
			if( 0 == 第一个元素 + 第二个元素 + 第三个元素 ){
				if(三元组是否不存在){
					将三元组添加到三元组结合;
				}
				返回元素下标
			}
		}
	}
}

完整示意代码

public List<List<Integer>> threeSum(int[] nums) {
	//暴力
	Set<List<Integer>> exet = new HashSet<>();
	List<List<Integer>> res = new ArrayList<>();
	//排序,从小到大
	Arrays.sort(nums);
	for (int a = 0; a < nums.length-2; a++) {
		//满足此条件后,后续的不再进行,因为已经大于0
		if(nums[a]+nums[a+1]+nums[a+2]>0 ) {
			return res;
		}
		for (int b = a + 1; b < nums.length-1; b++) {
			for (int c = b + 1; c < nums.length; c++) {
				if(nums[a] + nums[b] +nums[c] == 0) {
					List<Integer> temp = new ArrayList<>();
					temp.add(nums[a]);
					temp.add(nums[b]);
					temp.add(nums[c]);
					//判断是否重复
					if(!exet.contains(temp)) {
						exet.add(temp);
						res.add(temp);
					}
				}
			}
		}
	}	
	return res;
}

不过很可惜,在315/318个测试用例,我们超出了时间限制,暴力破解失败
在这里插入图片描述

3.2.Hash优化三重for循环

解题要素:
1、满足要求的基本条件:nums[a] + nums[b] + nums[c] = 0,因此得出:int c = - (nums[a] + nums[b]);
2、将第3次循环所得到的nums[c]放入到hashMap中,key为nums[c],value为nums[c]对应的下标c;
3、同一个元素只能使用一次,因此c>b>a;因此可以得出
for (a = 0)
for (b = a +1)
mapKeyC.get(nums[c]) > b
4、过滤无效数据:可以先对数组排序。排序之后,当最小的值nums[a]如果已经大于0,则nums[a] + nums[b] + nums[c] 一定大于0,不满足计算条件。
5、过滤重复项组合,可以先对数组排序。排序之后,相同值是靠在一起的,因此存在nums[b] = nums[b-1]时,nums[b]不用做处理,因为处理结果一定和nums[b-1]一样。
伪代码示意

排序,从小到大
for(){
	mapC.put(元素值,元素下标)}
for(第一个元素   循环条件 = 长度-2){
	if(三数之和大于0){
		结束寻找
	}
	//如 2,2,3,第一个元素2和第二个元素2重复,即2-1 3的组合和 2-2 3的组合一样,因此处理2-2 3的结果
	if(相邻第一个元素值不相等){
		for(第二个元素 = 第一个元素 + 1){
			if(相邻第二个元素不相等){
				目标值c = 0 - 元素a - 元素b
				if(mapC中有目标值c 并且c的下标大于b){
					将三元组添加到集合
				}
			}
		}
	}
}

完整代码示意

public List<List<Integer>> threeSum(int[] nums) {
    List<List<Integer>> list = new ArrayList<>();
	Arrays.sort(nums);
	Map<Integer, Integer> hash = new HashMap<>();
	for (int c = 0; c < nums.length; c++) {
	   hash.put(nums[c], c);
	}
	for (int a = 0; a < nums.length-2; a++) {
		//三数之和大于0
		if(nums[a]+nums[a+1]+nums[a+2]>0 ) {
		    return list;
		}
		//相邻元素相等不处理如:223时,22不处理,23才处理
		if ((a == 0 || nums[a] != nums[a-1])) {
			for (int b = a+1; b < nums.length; b++) {
			//同理相邻元素
				if (b == a+1 || nums[b] != nums[b-1]) {
				    int c = -(nums[a] + nums[b]);
				    if (hash.containsKey(c) && hash.get(c) > b) {
					    List<Integer> temp = new ArrayList<Integer>();
					    temp.add(nums[a]);temp.add(nums[b]);temp.add(c);
					    list.add(temp);
				    }
			    }
		    }
	    }
	}
    return list;
}

在这里插入图片描述

3.3.双指针:左右指针非同步收缩

对nums数组进行排序,使其从小到大排序。因为 A+B+C = 0;可得 A*(-1) = B + C
循环组数nums,固定当前遍历元素,剩下数组根据条件决定某一个指针中间收缩。
在这里插入图片描述
解题要素:
1、如果 nums[K]大于0,则结束全部循环,因为最小的一个数都大于0了,则任意数相加肯定大于0;
2、当 k > 0且nums[k] == nums[k - 1]时即跳过此元素nums[k],因为此时在nums[k - 1] 的匹配和nums[k]是一致的;
3.1、当nums[k] + nums[left] + nums[right] < 0 时,也就是说nums[left] + nums[right]的值需要增加,而nums[right]已经是最大值,因此需要增大nums[left],也就是left右移;
3.2、当nums[k] + nums[left] + nums[right] > 0 时,也就是说nums[left] + nums[right]的值需要减小,而nums[left]已经是最小了,因此需要减小nums[right],也就是right左移;
3.3、当nums[k] + nums[left] + nums[right] = 0,即为我们所需要的组合。
伪代码示意

排序
for(第一个元素K){
	if(K大于0){
		结束寻找
	}
	if(相邻值相等){
		跳过当前值
	}
	
	while (左指针 < 右指针){
		三元素之和
			大于0	:右指针左移
			小于0	:左指针右移
			等于0	:找到三元组 左指针右移,右指针左移,并且过滤掉相邻元素相等值
	}
}

public List<List<Integer>> threeSum3(int[] nums) {
	Arrays.sort(nums);
	List<List<Integer>> list = new ArrayList<>();
	for (int k = 0; k < nums.length; k++) {
		if (nums[k] > 0) {
			break;
		}
		if (k > 0 && nums[k] == nums[k-1]) {
			continue;
		}
		int left = k + 1;
		int right = nums.length - 1;
		while (left < right) {
			int sum = nums[k] + nums[left] + nums[right]; 
			if (sum < 0) {
				left ++;
			} else if (sum > 0) {
				right --;
			} else {
				List<Integer> listTemp = new ArrayList<>();
				listTemp.add(nums[k]);
				listTemp.add(nums[left]);
				listTemp.add(nums[right]);
				list.add(listTemp);
				while (left < right && nums[left] == nums[++ left]);
				while (left < right && nums[right] == nums[-- right]);     
			}
		}
	}	
	return list;
}

在这里插入图片描述

4.最接近的三数之和

给定一个包括 n 个整数的数组 nums 和 一个目标值 target。找出 nums 中的三个整数,使得它们的和与 target 最接近。返回这三个数的和。假定每组输入只存在唯一答案。
示例:
输入:nums = [-1,2,1,-4], target = 1
输出:2
解释:与 target 最接近的和是 2 (-1 + 2 + 1 = 2) 。
提示:
3 <= nums.length <= 10^3
-10^3 <= nums[i] <= 10^3
-10^4 <= target <= 10^4

public static int threeSumClosest(int[] nums, int target) {
	Arrays.sort(nums);
	int best = 10000000;
	//枚举a
	for (int a = 0; a < nums.length-2; a++) {
		// 保证和上一次枚举的元素不相等
		if (a > 0 && nums[a] == nums[a - 1]) {
			continue;
		}
		int left = a + 1;
		int right = nums.length-1;
		while (left < right) {
		int sum = nums[a] + nums[left] + nums[right];
		// 如果和为 target 直接返回答案
			if (sum == target) {
				return target;
			}
			// 在sum和best中,取最接近target的值
			if (Math.abs(sum - target) < Math.abs(best - target)) {
				best = sum;
			}
			if (sum > target) {
			// 如果和大于 target,右指针向左移动,减少sum值,使之趋于target
				right--;
			}else {
			// 如果和小于 target,左指针向右移动,增加sum值,使之趋于target
				left++;
			}
		}
	}
	return best;
}

5.四数之和

给你一个由 n 个整数组成的数组 nums ,和一个目标值 target 。请你找出并返回满足下述全部条件且不重复的四元组 [nums[a], nums[b], nums[c], nums[d]] :
0 <= a, b, c, d < n
a、b、c 和 d 互不相同
nums[a] + nums[b] + nums[c] + nums[d] == target
你可以按 任意顺序 返回答案 。
示例 1:
输入:nums = [1,0,-1,0,-2,2], target = 0
输出:[[-2,-1,1,2],[-2,0,0,2],[-1,0,0,1]]
示例 2:
输入:nums = [2,2,2,2,2], target = 8
输出:[[2,2,2,2]]
提示:
1 <= nums.length <= 200
-109 <= nums[i] <= 109
-109 <= target <= 109

	public List<List<Integer>> fourSum(int[] nums, int target) {
		List<List<Integer>> quadruplets = new ArrayList<List<Integer>>();
		if (nums == null || nums.length < 4) {
			return quadruplets;
		}
		Arrays.sort(nums);
		int length = nums.length;
		for (int i = 0; i < length - 3; i++) {
			//过滤掉相邻的相同元素
			if (i > 0 && nums[i] == nums[i - 1]) {
				continue;
			}
			//按照从小到大排序后,如果此时这四个值大于target,后面的值只会更加大于target
			if (nums[i] + nums[i + 1] + nums[i + 2] + nums[i + 3] > target) {
				break;
			}
			for (int j = i + 1; j < length - 2; j++) {
				if (j > i + 1 && nums[j] == nums[j - 1]) {
					continue;
				}
				if (nums[i] + nums[j] + nums[j + 1] + nums[j + 2] > target) {
					break;
				}
				int left = j + 1, right = length - 1;
				while (left < right) {
					int sum = nums[i] + nums[j] + nums[left] + nums[right];
					if (sum == target) {
						quadruplets.add(Arrays.asList(nums[i], nums[j], nums[left], nums[right]));
						while (left < right && nums[left] == nums[left + 1]) {
							left++;
						}
						left++;
						while (left < right && nums[right] == nums[right - 1]) {
							right--;
						}
						right--;
					} else if (sum < target) {
						left++;
					} else {
						right--;
					}
				}
			}
		}
		return quadruplets;
	}
  • 4
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值