1. Two Sum
Code it now! https://leetcode.com/problems/two-sum/
问题描述:
一个整数数组,找出某两个数加和等于某一个特定的数(target)。
要求函数twoSum返回符合要求的两数在数组中所处的位置,index1和index2(index1必须小于index2)。请注意,你所编写的函数返回值应该是下标加1.(本题只需要考虑仅有唯一解的情况)。
解题思路:
暴力算法:时间复杂度O(n^2),空间复杂度O(1)。
本题前提条件(仅有唯一解,且index1小于index2),故排除同一元素使用两次的情况,只需遍历数组中每个元素i,然后在剩余元素中查找是否存在j使得i+j=target,因为双重遍历数组,因此时间复杂度为n^2代码如下:
class Solution {
public int[] twoSum(int[] nums, int target) {
int index1 = 0;
int index2 = 0;
int[] res = new int[2];
//双重循环遍历每个元素是否满足要求
for(int i=0;i<nums.length;i++){
for(int j=i+1;j<nums.length;j++){
if(nums[i]+nums[j]==target){
index1 = i;
index2 = j;
break;
}
}
}
res[0] = index1;
res[1] = index2;
return res;
}
}
哈希表法:时间复杂度O(n),空间复杂度O(n)。
通过HashMap把元素值和索引存储起来,因为HashMap检索值的时间复杂度为O(1),故实现了空间换时间,代码如下:
class Solution {
public int[] twoSum(int[] nums, int target) {
int index1 = 0;
int index2 = 0;
int[] res = new int[2];
Map<Integer,Integer> map = new HashMap<Integer,Integer>();
for(int i=0;i<nums.length;i++){
//如果对于nums[i],map中已存在值target-nums[i]则获取当中的索引,并跳出循环,否则将当前值put进map中。
if(map.containsKey(target-nums[i]))
{
index1 = map.get(target-nums[i]);
index2 = i;
break;
}
else
{
map.put(nums[i],i);
}
}
res[0] = index1;
res[1] = index2;
return res;
}
}
167.Two Sum II - Input array is sorted
Code it now! https://leetcode.com/problems/two-sum-ii-input-array-is-sorted/
问题描述:
给定有序数组和target找到两数之和为target的两数下标。
解题思路:
遍历算法:时间复杂度O(n),空间复杂度O(1)。
本题用到两指针法,因为数组已排好序,因此只需要两指针一前一后定位到两元素之和为target的两元素,并记录下元素下标即可求解。代码如下:
class Solution {
public int[] twoSum(int[] numbers, int target) {
int start = 0;
int end = numbers.length-1;
int index1 = 0;
int index2 = 0;
while(true)
{
//双指针寻值操作
if(numbers[start]+numbers[end]==target){
index1 = start+1;
index2 = end+1;
break;
}else if(numbers[start]+numbers[end]>target){
end--;
}else{
start++;
}
}
int[] res = {index1,index2};
return res;
}
}
15. 3Sum
Code it now! https://leetcode.com/problems/3sum/
问题描述:
要求找出所有不重复组合。
解题思路:
暴力算法:三层for循环,时间复杂度O(n^3),还需要删除重复答案显然不是合适解法。
优化算法:类比Two-Sum问题,我们想到能否在这三个数当中固定一个数i,剩下两位数之和为i的相反数。
我们可以先对数组进行排序,固定一个数i,并在剩下数组中求两数之和为-i,最后用两个指针分别从前后两端向中间搜索即可(此题需要考虑两处结果集重复问题)。时间复杂度为O(n^2),代码如下:
class Solution {
public List<List<Integer>> threeSum(int[] nums) {
List<List<Integer>> res = new ArrayList<List<Integer>>();
if(nums.length<3)
return res;
Arrays.sort(nums);
for(int i=0;i<nums.length-2;i++){
//此步骤也为去重操作。
if(i>0&&nums[i]==nums[i-1])
continue;
findTwosum(nums,i+1,nums.length-1,nums[i],res);
}
return res;
}
public static void findTwosum(int[] nums,int start,int end,int value,List<List<Integer>> res){
while(start<end){
//找到了满足条件的解,则用新的list存储起来,并加入到res解集中。
if(nums[start]+nums[end]+value==0){
List<Integer> temp = new ArrayList<Integer>();
temp.add(nums[start]);
temp.add(nums[end]);
temp.add(value);
res.add(temp);
//题目要求结果不可重复,此步骤为去重操作。
while(start<end&&nums[start]==nums[start+1])
start++;
start++;
while(start<end&&nums[end]==nums[end-1])
end--;
end--;
}else if(nums[start]+nums[end]+value>0){
end--;
}else{
start++;
}
}
}
}
元素已排好序,通过对前后相邻元素判断并进行跳过的操作,能够有效避免解集重复的问题。注意看注释两个去重操作。
此3sum的去重操作也可采用Set集合来实现,从而减少了两层逻辑判断去除重复,代码如下,亦是4sum解题框架。
class Solution {
public List<List<Integer>> threeSum(int[] nums) {
List<List<Integer>> list = new ArrayList<List<Integer>>();
Set<List<Integer>> res = new HashSet<List<Integer>>();
if(nums.length<3)
return list;
Arrays.sort(nums);
//首先固定数组中的一个元素。
for(int i=0;i<nums.length-2;i++){
int left = i+1;
int right = nums.length-1;
//在剩余数组中寻找另外两个元素,得到满足三数和为0的解,并存入解集。解集用的Set因此能直接过滤掉重复解。不必复杂逻辑判断。
while(left<right)
{
int sum = nums[i]+nums[left]+nums[right];
if(sum==0)
{
List<Integer> temp = new ArrayList<Integer>();
temp.add(nums[i]);
temp.add(nums[left]);
temp.add(nums[right]);
res.add(temp);
left++;
right--;
}
else if(sum<0)
left++;
else
right--;
}
}
//将Set中的解集,转换成List中去。
for(List<Integer> temp:res){
list.add(temp);
}
return list;
}
}
18. 4Sum
Code it now! https://leetcode.com/problems/4sum/
问题描述:
要求找出所有不重复组合。
解题思路:
前面的2sum用到的是固定其中一个值,然后在剩余数组中寻找另一个值;3sum用到的是固定其中一个值,然后在剩余已排好序的数组中寻找另外两个值是否能够满足条件,时间复杂度是O(n^2);其实4sum的解题思路也就是相比于3sum多一层for循环,固定前两个值,然后在剩下排好序的数组中寻找另外两个值,时间复杂度是O(n^3)。
此题4sum有用到HashSet来避免解集重复问题,从而降低了算法的思维难度。算法如下:
class Solution {
public List<List<Integer>> fourSum(int[] nums, int target) {
List<List<Integer>> List = new ArrayList<List<Integer>>();
Set<List<Integer>> res = new HashSet<List<Integer>>();
if(nums.length<4)
return List;
Arrays.sort(nums);
//两层for循环可先选出数组中两个不同元素,然后在剩下排好序的数组中选另外两个不同元素。
for(int i=0;i<nums.length-3;i++){
for(int j=i+1;j<nums.length-2;j++){
int left = j+1;
int right = nums.length-1;
//在剩下数组中选出另外两个元素。
while(left<right)
{
//当选到符合题意的单个解时,此处用到Set来保存,从而很简洁的排出了解集元素重复的问题。
int sum = nums[i] + nums[j] + nums[left] + nums[right];
if (sum == target)
{
List<Integer> temp = new ArrayList<Integer>();
temp.add(nums[i]);
temp.add(nums[j]);
temp.add(nums[left]);
temp.add(nums[right]);
res.add(temp);
++left; --right;
}
else if (sum < target)
++left;
else
--right;
}
}
}
//将存在Set中的解集转成存在List中,并return。
for(List<Integer> temp:res){
List.add(temp);
}
return List;
}
}
前面所讲到的k-sum问题,都是一个可用一个框架来搞定,也就是固定某值,找剩余值法。
26.Remove Duplicates from Sorted Array
Code it now! https://leetcode.com/problems/remove-duplicates-from-sorted-array/
问题描述:
给定一个有序数组,删除重复内容,使每个元素只出现一次,并返回新的长度。
数组是有序的,只能使用O(1)的额外空间,且不重复元素有序排列在数组中。
解题思路:
哈希表算法:时间复杂度O(n),空间复杂度O(n),不符合题意。
双指针法:时间复杂度O(n),空间复杂度O(1)。
本题当中数组是有序的,采用两个指针索引计数操作。代码如下:
class Solution {
public int removeDuplicates(int[] nums) {
if (nums.length == 0) return 0;
int i = 0;
//双指针循环遍历数组。
for (int j = 1; j < nums.length; j++) {
if (nums[j] != nums[i]) {
i++;
nums[i] = nums[j];
}
}
return i + 1;
}
}
27.Remove Element
Code it now! https://leetcode.com/problems/remove-element/
问题描述:
给定一个有序数组和一个target,删除数组中的target并返回新长度。
只能使用O(1)的额外空间,且在新长度数组中,排列好非target元素。
解题思路:
双指针法:时间复杂度O(n),空间复杂度O(1)。
本题采用双指针,一个指针保证了新长度数组的元素排列好非target元素(可在原数组中进行操作,获得新数组,以该指针作为标记),另一指针迅速遍历整个数组。代码如下:
class Solution {
public int removeElement(int[] nums, int val) {
if(nums.length==0) return 0;
//双指针操作,一个用于替换原数组的值,一个用于不断向后遍历整个数组。
int i=0;
for(int j=0;j<nums.length;j++){
if(nums[j]!=val){
nums[i++]=nums[j];
}
}
return i;
}
}
34.Search for a Range
Code it now! https://leetcode.com/problems/search-for-a-range/
问题描述:
算法时间复杂度为O(log n)。
解题思路:
二分查找法:时间复杂度O(log n),空间复杂度O(1)。
本题使用了二分搜索的思想,一般遇到O(log n)时间复杂度的算法大多有涉及到二分思想。因为数组是有序的,因此在二分搜索到目标值之后,分别向前向后找到目标值的开始和结束索引即为所求。代码如下:
class Solution {
public int[] searchRange(int[] nums, int target) {
int[] res = {-1,-1};
int start = 0;
int end = nums.length-1;
int i=0;
int j=0;
//二分搜索框架。
while(start<=end){
int mid = (start+end)/2;
if(nums[mid]==target)
{
//从目标值索引位置往前找。
for(i=mid-1;i>=start;i--){
if(nums[i]!=target)
break;
}
res[0]=i+1;
//从目标值索引位置往后找。
for(j=mid+1;j<=end;j++){
if(nums[j]!=target)
break;
}
res[1]=j-1;
return res;
}
else if(nums[mid]>target){
end = mid-1;
}
else{
start = mid+1;
}
}
return res;
}
}
35.Search Insert Position
Code it now! https://leetcode.com/problems/search-insert-position/
问题描述:
给定有序数组和target,找到target则返回索引,否则返回按顺序插入的位置。
数组无重复元素。
解题思路:
二分查找法:时间复杂度O(log n),空间复杂度O(1)。
本题使用了二分搜索的思想,在二分搜索框架代码中进行一定操作即可。代码如下:
class Solution {
public int searchInsert(int[] nums, int target) {
int start = 0;
int end = nums.length-1;
int mid = 0;
while(start<=end)
{
mid = (start+end)/2;
if(nums[mid]==target){
return mid;
}
else if(nums[mid]>target)
end = mid-1;
else
start = mid+1;
}
//二分搜索没找到target,则最后索引为所求需要插入的位置。
return start;
}
}
55.Jump Game
Code it now! https://leetcode.com/problems/jump-game/
问题描述:
给定一个非负整数数组,最初定位在数组第一个索引处,数组中每个元素值表示在当前位置所能跳跃的最大长度,确定能否跳到最后一个位置。
解题思路:
贪心算法:时间复杂度O(n),空间复杂度O(1)。
本题使用了贪心策略,用farthest记录当前所在位置所能跳跃的最远距离,如果当前所能跳跃的最远距离小于当前的索引位置则为不可达返回false,如果当前所能跳跃的最远距离大于等于数组大小则能够跳到最远位置返回true。代码如下:
class Solution {
public boolean canJump(int[] nums) {
int farthest = 0;
boolean res = false;
for(int i=0;i<nums.length;i++)
{
if(farthest<i){
res = false;
break;
}
//遍历局部最优,即整体能跳最远距离。
farthest = Math.max(farthest,nums[i]+i);
if(farthest>=nums.length-1){
res = true;
break;
}
}
return res;
}
}
更通俗版贪心算法如下:
class Solution {
public boolean canJump(int[] nums) {
//贪心算法
int len = nums.length;
int max = -1;
int temp = 0;
// 保存每步能跳到的最大位置, 如果能够大于等于数组长度-1 那么就能表示能够跳到。
for(int i=0;i<len-1;i++){
max = Math.max(max,i+nums[i]);
if(i==temp){
temp = max;
}
}
if(temp<len-1) return false;
else return true;
}
}
45.Jump Game II
Code it now! https://leetcode.com/problems/jump-game-ii/
问题描述:
给定一个非负整数数组,最初定位在数组第一个索引处,数组中每个元素值表示在当前位置所能跳跃的最大长度,确定能够跳到最后一个位置的最少跳跃次数。
解题思路:
dp算法:时间复杂度O(n^2),空间复杂度O(n)。
本题使用动态规划策略(最优解的每一个子过程都是当前过程的最优解)。循环遍历数组,并对数组中的元素依次依据前面得到的结果进行优化,大致理解为第n个元素得到的结果是有前n-1个元素所决定的,层层更替结果。代码如下:
class Solution {
public int jump(int[] nums) {
//备忘录,依次保存跳跃至当前元素所在位置所需要的最少跳跃步数。
int len = nums.length;
int[] minJump = new int[len];
for(int i=0;i<len;i++){
minJump[i] = i;
for(int j=0;j<i;j++){
//动态规划递推公式,当前面元素位置能跳到位置i时,就进行跳到位置i的最小跳跃次数更新。
if(j+nums[j] >= i)
minJump[i] = Math.min(minJump[i],minJump[j]+1);
}
}
return minJump[len-1];
}
}
此算法在LeetCode中超时,因为两层循环遍历效率很低,需要剪枝优化。
贪心算法:时间复杂度O(n),空间复杂度O(1)。
本题使用贪心策略,需要在跳跃游戏一当中,进行最小跳跃步数纪录优化操作。代码如下:
class Solution {
public int jump(int[] nums) {
int curFarthest = 0;
int curEnd = 0;
int step = 0;
for(int i=0;i<nums.length-1;i++){
//得到当前元素所能跳跃到的最远位置(当前元素不能跳那么远,但是前面有元素能跳更远则认为当前元素也能跳跃到更远的位置)。
curFarthest = Math.max(curFarthest,nums[i]+i);
//到达计步点时,步数加一,并更新curEnd,即下一次开始计步点位置。
if(curEnd==i){
step++;
curEnd=curFarthest;
}
}
return step;
}
}
53.Maximum Subarray
Code it now! https://leetcode.com/problems/maximum-subarray/
问题描述:
在一个数组中找到连续的子数组(至少包含一个数字),这个数组的总和最大。简称LIS问题,与其类似的有LCS等问题。
解题思路:
dp算法:时间复杂度O(n),空间复杂度O(n)。
本题使用动态规划策略(最优解的每一个子过程都是当前过程的最优解),根据递推公式,写出递推方程为
dp[i] = nums[i]+(dp[i-1]>0?dp[i-1]:0);
dp[i] = nums[i]+(dp[i-1]>0?dp[i-1]:0);
以此公式为核心,不断更新备忘录解集,从而得到最优解。
class Solution {
public int maxSubArray(int[] nums) {
int len = nums.length;
int[] dp = new int[len];
int max = -1;
//使用动态规划的记忆化搜索
for(int i=0;i<len;i++){
//递推当成,不断更新解,如果前面元素相加和小于0则赋值0重新开始累加,大于0则继续计算max的最大值。
dp[i] = nums[i]+(dp[i-1]>0?dp[i-1]:0);
max = Math.max(max,dp[i]);
}
return max;
}
}
class Solution {
public int maxSubArray(int[] nums) {
int len = nums.length;
int[] dp = new int[len];
int max = -1;
//使用动态规划的记忆化搜索
for(int i=0;i<len;i++){
//递推当成,不断更新解,如果前面元素相加和小于0则赋值0重新开始累加,大于0则继续计算max的最大值。
dp[i] = nums[i]+(dp[i-1]>0?dp[i-1]:0);
max = Math.max(max,dp[i]);
}
return max;
}
}
66.Plus One
Code it now! https://leetcode.com/problems/plus-one/
问题描述:
该整数不包含任何前导0,就是nums[0]!=0,除非数字0本身。
解题思路:
遍历算法:时间复杂度O(n),空间复杂度O(1)。
本题主要考查在加一过程中的进位操作,比如数组表示数字999,那么执行加一操作后整个数字变为1000,也就是数组的大小也增加了一位,数组原来各位元素也发生了相应的改变。若不产生进位,那么在数组末尾元素加一即完成操作。代码如下:
class Solution {
public int[] plusOne(int[] digits) {
for(int i=digits.length-1;i>=0;i--){
if(digits[i]+1==10){
digits[i]=0;
if(i==0){
int[] res = new int[digits.length+1];
//数组copy函数,digits从下标0开始,res从下标1开始,复制digits.length位数字从digits数组到res数组。
System.arraycopy(digits, 0, res, 1, digits.length);
res[0]=1;
digits = res;
}
}else{
//未产生进位情况,直接在数组最后一个元素加一,break出循环。
digits[i]++;
break;
}
}
return digits;
}
}
78.Subsets
Code it now! https://leetcode.com/problems/subsets/
问题描述:
解题思路:
回溯算法:时间复杂度O(2^n),空间复杂度O(1)。
本题用到回溯法(深度优先搜索策略)。
引入:寻求问题的解的一种可靠方法是首先列出所有候选解,然后依次检查每一个,在检查完所有或部分候选解后,即可找到需要的解。
回溯法设计流程是:
1.定义解向量和每个分量的取值范围,解向量为<x1,x2,......xn>
作用:可搜索一个问题的所有解或任一解。
2.确定每个结点分支的约束条件,即我们需要的解,从所有解当中提取出来。
作用:从所有检索中,找出满足题意的解。
3.确定搜索策略:一般是深度优先策略。
此题为求解子集问题,按照回溯法设计思想进行操作代码如下:
class Solution {
public List<List<Integer>> subsets(int[] nums) {
List<List<Integer>> subsets = new ArrayList<List<Integer>>();
List<Integer> temp = new ArrayList<Integer>();
if(nums.length==0)
return subsets;
//第一步:定义解向量
findSubsets(nums,0,temp,subsets);
return subsets;
}
//递归加循环的回溯法。
public static void findSubsets(int[] nums,int start,List<Integer> temp,List<List<Integer>> subsets){
//第二步:确定分支条件,选出题目需要的解集
subsets.add(new ArrayList<Integer>(temp));
//第三步:深度优先搜索
for(int i=start;i<nums.length;i++){
temp.add(nums[i]);
findSubsets(nums,i+1,temp,subsets);
//回溯法的核心步骤,在遍历完后回退至上一层。
temp.remove(temp.size()-1);
}
return ;
}
}
80.Remove Duplicate from sorted array II
Code it now! https://leetcode.com/problems/remove-duplicates-from-sorted-array-ii/
问题描述:
给定有序数组中的重复元素,元素最多允许出现两次,返回新数组长度L,且数组前L个元素有序排列。
解题思路:
遍历算法:时间复杂度O(n),空间复杂度O(1)。
本题用到两指针法,一个用于更新新数组元素,另一个循环遍历数组,代码操作如下,这类题需要一定设计。
class Solution {
public int removeDuplicates(int[] nums) {
int i=0;
//i指针用于新数组元素更替,j指针用于快速遍历数组。
for (int j=0;j<nums.length;j++)
{
//在j指针遍历数组的过程中,有两种情况需要考虑的是,一种是数组元素小于等于2个时,i指针需要不断向后移动;另一种是j指针已经移动到第3个元素之后了,此时只有当当前元素跟它前两个元素不等时,i指针才会向后更新新数组的元素值。可以自己用笔模拟1 1 1 1 1 2 2 这个过程就能理解透彻了。
if (j < 2 || nums[j] > nums[i - 2])
nums[i++] = nums[j];
}
return i;
}
}
这里也顺便给出只允许元素出现一次的代码操作代码。见leetcode第26题,Remove Duplicate from sorted array.
class Solution {
public int removeDuplicates(int[] nums) {
/*
if (nums.length == 0) return 0;
int i = 0;
//双指针循环遍历数组。
for (int j = 1; j < nums.length; j++) {
if (nums[j] != nums[i]) {
i++;
nums[i] = nums[j];
}
}
return i + 1;
}*/
//优化版
int i=0;
//i指针用于更新新数组元素,j元素用于遍历整个数组。
for(int j=0;j<nums.length;j++)
{
//当满足遍历的是第一个元素或者当前元素不等于此元素的前一位元素时,新数组元素就需要更新。
//手写模拟操作1 1 1 2 2 3能够很快理解此操作。
if(j < 1 || nums[j] != nums[i-1])
nums[i++] = nums[j];
}
return i;
}
}
class Solution {
public int removeDuplicates(int[] nums) {
/*
if (nums.length == 0) return 0;
int i = 0;
//双指针循环遍历数组。
for (int j = 1; j < nums.length; j++) {
if (nums[j] != nums[i]) {
i++;
nums[i] = nums[j];
}
}
return i + 1;
}*/
//优化版
int i=0;
//i指针用于更新新数组元素,j元素用于遍历整个数组。
for(int j=0;j<nums.length;j++)
{
//当满足遍历的是第一个元素或者当前元素不等于此元素的前一位元素时,新数组元素就需要更新。
//手写模拟操作1 1 1 2 2 3能够很快理解此操作。
if(j < 1 || nums[j] != nums[i-1])
nums[i++] = nums[j];
}
return i;
}
}
442.Find All Duplicates in an Array
Code it now! https://leetcode.com/problems/find-all-duplicates-in-an-array/
问题描述:
给定一个数组元素值在[1,n]之间,一些元素出现两次其它元素出现一次,找出所有重复出现两次的元素。
解题思路:
元素标记算法:时间复杂度O(n),空间复杂度O(1)。
本题数组的数据的特点是元素值均在[1,n]之间,这样会有部分元素值是缺失的。 数组下标范围是[0,n-1],我们可以想到的是如果把所有数组元素值-1,那么正好对应数组所有下标,并且使操作得到新下标的元素值为这个新下标的相反数,那么在遍历数组遇到重复元素值的时候,该元素值-1获得的下标所对应的元素值为负数,为我们需要找的目标值。不重复元素,并不会产生这样的冲突,从而出现两次的元素被这种标记思想给挑选了出来。代码如下:
class Solution {
public List<Integer> findDuplicates(int[] nums) {
//元素标记法
List<Integer> res = new ArrayList<Integer>();
for(int i=0;i<nums.length;i++)
{
//首先令每个元素值转成新下标值,如果新下标值对应的元素值为负数,表明此元素已出现过,添加到res中,否则对该新下标的元素值设置成负数。
int val = Math.abs(nums[i])-1;
if(nums[val]<0)
res.add(Math.abs(nums[i])); //避免下一步操作将对应重复位置的元素变为负数
nums[val] = -nums[val];
}
return res;
}
}
448.Find All Numbers Disappeared in an Array
Code it now! https://leetcode.com/problems/find-all-numbers-disappeared-in-an-array/
问题描述:
给定一个整数数组,其中1<=a[i]<=n,一些元素出现两次,其它出现一次。
找出在数组[1,n]中没有出现的元素,空间复杂度为O(1),时间复杂度为O(n)。
解题思路:
元素标记算法:时间复杂度O(n),空间复杂度O(1)。
本题和442题Find All Duplicates in an Array很相似找出所有重复数字,这里是找出没有出现的数字,因为某些元素出现两次,所以在[1,n]中有部分元素并未出现。本题如果用set集合进行过滤,很便捷就能得到结果,此处空间复杂度限制为O(1),则需要另谋他径。这里也用到的是元素标记法。代码逻辑如下:
class Solution {
public List<Integer> findDisappearedNumbers(int[] nums) {
//标记出现过的元素大法。。。
List<Integer> list = new ArrayList<Integer>();
for(int i=0;i<nums.length;i++){
//这里同样是把所有元素-1变成所有下标,然后这些下标的值全部赋值成负数,没有出现的下标自然就是正数。
//举例如下:数组元素为{1,1,2,3,3,4} 对应下标为{0,0,1,2,2,3} 这些下标对应的元素全变成负数新数组为{-1,-1,-2,-3,3,4}这样就可以发现下标为4,5对应的元素为正数,在后面遍历新数组找出值大于0的对应的下标值+1即为所求。 所谓元素标记就是把出现过的元素所对应的下标的元素值标记为负数,未出现的为正数,然后找出来即可。
//
int val = Math.abs(nums[i])-1;
if(nums[val]>0){
nums[val] = -nums[val];
}
}
for(int j=0;j<nums.length;j++){
//然后在遍历新数组,新数组值大于0所对应的下标值+1即为数组缺失的整数值。
if(nums[j]>0){
list.add(j+1);
}
}
return list;
}
}
169.Majority Element
Code it now! https://leetcode.com/problems/majority-element/
问题描述:
解题思路:
栈算法:时间复杂度O(n),空间复杂度O(n)。
本题可使用辅助栈实现,栈的声明是Stack<Object> stack = new Stack<Object>(),常用栈的几个方法是
empty():栈是否为空。
peek():查看栈顶元素,返回该元素。
push():压入元素至栈顶。
pop():移除栈顶元素并返回该元素。
不断往栈中添加和删除元素,当栈为空时,添加当前元素;当栈不为空时,查看当前元素和栈顶元素是否相等,如果想等则继续压入该元素,如果不相等则将栈顶元素弹出。最后栈中仅剩下数组中出现次数大于一半的元素。代码如下:
class Solution {
public int majorityElement(int[] nums) {
Stack<Integer> stack = new Stack<Integer>();
for(int i=0;i<nums.length;i++)
{
//如果当前栈为空,压入当前元素,并continue进入到下次循环。
if(stack.empty()){
stack.push(nums[i]);
continue;
}
//判断当前元素与栈顶元素是否相等,如果不相等,弹出一个栈顶元素;如果相等,压入当前元素。
if(stack.peek()!=nums[i]){
stack.pop();
}else{
stack.push(nums[i]);
}
}
return stack.peek();
}
}
哈希表算法:时间复杂度O(n),空间复杂度O(n)。
本题也可以使用HashMap实现,使用键值对来统计元素出现次数。涉及到了HashMap的相同key值的value值更新和遍历。代码如下:
class Solution {
public int majorityElement(int[] nums) {
int result = 0;
Map<Integer,Integer> map = new HashMap<Integer,Integer>();
for(int i=0;i<nums.length;i++){
//判断当前key值是否存在,如果不存在,value初值设置为1;如果存在,value值在原来值的基础上加一。
if(map.containsKey(nums[i])){
map.put(nums[i],map.get(nums[i])+1);
}else{
map.put(nums[i],1);
}
}
//使用迭代器遍历map,value值大于数组长度二分之一的对应key值为我们所求。
Iterator iter = (Iterator) map.keySet().iterator();
while(iter.hasNext()){
int key = (int) iter.next();
int value = map.get(key);
if(value>nums.length/2){
result = key;
}
}
return result;
}
}
遍历算法:时间复杂度O(n),空间复杂度O(1)。
使用纯数组操作,用count来计数(用count计数模拟栈操作),详细解释见注释。
class Solution {
public int majorityElement(int[] nums) {
int count = 0;
int res = -1;
for(int i=0;i<nums.length;i++)
{
//当count为0的时候,此时需要为count计数加一,同时令res为当前元素。不为0时,如果当前元素不等于res值,那么count减1操作,等于时count加1操作,因为真正的res出现次数是过半的,所以最后count大于0时所对应的res为我们所要找的元素。
if(count==0){
count++;
res = nums[i];
}else if(nums[i]!=res){
count--;
}else{
count++;
}
}
return res;
}
}
229.Majority Element II
Code it now! https://leetcode.com/problems/majority-element-ii/
问题描述:
解题思路:
栈算法:时间复杂度O(n),空间复杂度O(1)。
本题和169题Majority Element有着很大的不同,是否存在共性的方法呢?前面没有空间限制,处理的用到了栈和Hashmap辅助空间来实现。本题用HashMap还是很容易实现,这里不可行;对数组排序,双指针也能很快求出,仍然不可行!!!于是169题应有空间复杂度O(1)的方法(已追加)。169题使用count来对出现次数超过1/2的数进行计数,类比过来,超过n/3的元素在数组中可能有1个或者是2个,我们使用count1和count2两个计数器进行计数,(仔细想想不难发现,如果假设数组有90个元素,满足条件的元素至少需要出现31次,因为有count1和count2计数器,数组元素的计数变成了count1处理45个元素,count2处理45个元素,在45个元素中出现31次是过半的,因此符合169题的寻找过半元素操作,一番操作下来,原问题转化成了把数组分成两半,在各自一半中寻找过半元素。没错,就是这样!),因此同样可以起到过滤作用,筛选出我们可能需要的元素,再通过一层循环判断是否为我们需要的结果即可。代码如下:
class Solution {
public List<Integer> majorityElement(int[] nums) {
if(nums.length==0)
return new ArrayList<Integer>();
List<Integer> res = new ArrayList<Integer>();
int number1 = nums[0];
int number2 = nums[0];
int count1 = 0;
int count2 = 0;
for(int i=0;i<nums.length;i++)
{
//本题最多2个解。
//count1和count2 把整个数组的元素出现次数统计划分成了一半,因而类似找过半元素的操作即可找出可能解number1和number2
if(nums[i]==number1){
count1++;
}else if(nums[i]==number2){
count2++;
}else if(count1==0){
count1 = 1;
number1 = nums[i];
}else if(count2==0){
count2 = 1;
number2 = nums[i];
}else{
count1--;
count2--;
}
}
count1 = 0;
count2 = 0;
//对可能解number1和number2进行统计检验是否为可行解。
for(int j=0;j<nums.length;j++)
{
if(nums[j]==number1)
count1++;
else if(nums[j]==number2)
count2++;
}
if(count1>nums.length/3)
res.add(number1);
if(count2>nums.length/3)
res.add(number2);
return res;
}
}
215.Kth Largest Element in an Array
Code it now! https://leetcode.com/problems/kth-largest-element-in-an-array/
问题描述:
在未排序数组中找到第K大的元素,1<=K<=nums.length。
解题思路:
排序算法:时间复杂度O(nlog n),空间复杂度O(1)。
对数组进行排序,并输出相应位置,无脑做法,代码如下:
class Solution {
public int findKthLargest(int[] nums, int k) {
Arrays.sort(nums);
return nums[nums.length-k];
}
}
分治算法:时间复杂度O(n),空间复杂度O(1)。
显然本题不是通过排序投机取巧来处理,比较好理解的是分治算法来处理,我们需要找的是第K大元素,我们把问题等价转换成找第N-K+1小问题。在快速排序思想中我们知道,每次都能至少确定某个元素位置(一般以数组第一个元素作为中间值),于是本题就是利用了这种方式操作的,代码如下:
class Solution {
public int findKthLargest(int[] nums, int k) {
return findKth(nums,0,nums.length-1,nums.length-k+1);
}
//可类比快速排序中取得partion值的算法过程
public static int findKth(int[] nums,int l,int r,int k) {
int p = l;
int left = l;
int right = r;
//把整个数组依据数组第一个元素值进行划分成两块,左边都小于该值,右边都大于该值。
while(left<right)
{
while(left<right&&nums[right]>=nums[p])
right--;
while(left<right&&nums[left]<=nums[p])
left++;
swap(nums,left,right);
}
swap(nums,l,left);
//因为1<=k<=nums.length,0<=left<=nums.length-1,因此需要进行加一操作再比较判断。
if(left+1 == k)
return nums[left];
//k比较大,说明在右边一侧继续寻找。
else if(left<k)
return findKth(nums,left+1,r,k);
//k比较小,说明在左边一侧继续寻找。
else
return findKth(nums,l,left-1,k);
}
//交换数组两元素值。
public static void swap(int[] nums,int left,int right) {
int temp = 0;
temp = nums[left];
nums[left] = nums[right];
nums[right] = temp;
}
}
上面算法的过程可进一步升级成BFPRT算法,因为这里每次取做中间值的元素都是数组第一个元素,显然在糟糕情况下,算法的效率还是堪忧。BFPRT的核心思想就是在选取做中间值元素的时候选取了五分中位数的中位数作为中间值,能够保证此算法能够在线性时间得到需要的结果。有兴趣的可以自行百度BFPRT,哈哈哈哈哈。
414.Third Maximum Number
Code it now! https://leetcode.com/problems/third-maximum-number/
问题描述:
给定一个非空数组,返回数组第三大元素,如果不存在则返回最大元素。
解题思路:
排序算法:时间复杂度O(n),空间复杂度O(1)。
本题和215题Kth Largest Element in an Array非常相似,但是不可直接用215的方法进行求解,需要对本题所给数组进行去重处理。在去重后的新数组中,首先判断新数组元素个数,对于小于等于2个元素的数组先进行预处理,方可使用分治算法进行求解。本人用了个很蠢的办法进行拼接求解。第一步,去除数组中重复元素(因为数组无序,以前有道题所用到的双指针创造一个无重复数字的数组不可实现,用了set去重!!!很蠢);第二步,利用到冒泡排序思想,上浮三次即可找到第三大元素。整体时间复杂度为O(n),代码如下:
class Solution {
public int thirdMax(int[] nums) {
int temp = 0;
int count = 0;
int j = 0;
Set<Integer> set = new HashSet<Integer>();
for(int i=0;i<nums.length;i++){
set.add(nums[i]);
}
//把数组放入set然后再赋值出来,实现数组的去重操作。
Iterator iter = (Iterator)set.iterator();
while(iter.hasNext()){
nums[j++] = (int)iter.next();
}
//对新数组只有1-2个元素情况的处理。
if(set.size()==1){
return nums[0];
}else if(set.size()==2){
return Math.max(nums[0],nums[1]);
}else{
//新数组剩余元素3个以上的情况,采用了三次冒泡大法,得以找到第三大的数。
while(count<3){
for(int i=0;i<set.size()-count-1;i++){
if(nums[i]>nums[i+1]){
temp = nums[i];
nums[i] = nums[i+1];
nums[i+1] = temp;
}
}
count++;
}
return nums[set.size()-3];
}
}
这个方法很蠢,但是总算顺利求解,好办法加载中。。。
665.Non-decreasing Array
Code it now! https://leetcode.com/problems/non-decreasing-array/
问题描述:
给定一个有n个整数的数组,你的任务是通过至多修改一个元素来检查该数组是否可以不减少。
解题思路:
排序算法:时间复杂度O(n),空间复杂度O(1)。
本题简单的挨个前后元素统计是否存在逆序,如果逆序小于等于1则认为可调整,大于1认为不可调整是错误的。假设数组为{5,6,1,2,3,4,5,6},前后产生逆序只有一次,但是不可通过只修改一个元素来实现数组为非递减数组。于是乎,我们在判断前后元素大小关系时,还需要对元素位置进行一定调整。于是我们想到,如果以i下标做基准,i-1的元素值大于i+1元素时,此时需要令第i+1位置的元素值赋值为i-1位置的值,然后继续进行数组是否还有逆序,如果不大于则令i的元素值等于i+1位置的元素值。{5,6,1,2,3,4,5,6}和{5,6,4,7,8,9} 从这两个数组我们能够模拟出为什么我们要这样调整。避免产生错误结果。代码如下:
class Solution {
public boolean checkPossibility(int[] nums) {
int len = nums.length;
int count = 0;
for(int i=0;i+1<len;i++){
if(nums[i]>nums[i+1]){
count++;
//对产生逆序的元素进行处理,3 4 2 3 当i=1,且nums[2]<nums[0]调整时需要令nums[2]=nums[1],nums[2]>nums[0]则令nums[1]=nums[2]
//先进行处理保持当前元素继续有序之后(调整位置),再继续查找是否还存在逆序现象,如果还存在则会令count增加。
if(i>0&&nums[i-1]>nums[i+1])
{
nums[i+1] = nums[i];
}else{
nums[i] = nums[i+1];
}
}
}
return count<=1;
}
}
665.待追加。。。
Code it now! https://leetcode.com/problems/non-decreasing-array/
问题描述:
给定一个有n个整数的数组,你的任务是通过至多修改一个元素来检查该数组是否可以不减少。
解题思路:
排序算法:时间复杂度O(n),空间复杂度O(1)。
本题简单的挨个前后元素统计是否存在逆序