初级算法之数组

总结:数组问题在初级算法层次我认为要关注两种基本方法:数组排序和双指针,有些问题在对数组排序之后会让问题变得简单,例如题目4,题目9。双指针的应用例如题目8,题目9,题目11。而题目9:两数之和则是排序和双指针的综合应用。

题目1:从排序数组中删除重复项

给定一个排序数组,你需要在原地删除重复出现的元素,使得每个元素只出现一次,返回移除后数组的新长度。

不要使用额外的数组空间,你必须在原地修改输入数组并在使用 O(1) 额外空间的条件下完成。

示例 1:

给定数组 nums = [1,1,2],

函数应该返回新的长度 2, 并且原数组 nums 的前两个元素被修改为 1, 2。

你不需要考虑数组中超出新长度后面的元素。

示例 2:

给定 nums = [0,0,1,1,1,2,2,3,3,4],

函数应该返回新的长度 5, 并且原数组 nums 的前五个元素被修改为 0, 1, 2, 3, 4。

你不需要考虑数组中超出新长度后面的元素。

思路:每个元素和前一个元素相比较,若相同,则移除,若不同,则继续向后遍历。

int removeDuplicates(vector<int>& nums)
{
	for(int i=0;i<nums.size();i++)
	{
		if(i>0&&nums[i]==nums[i-1])
		{
			nums.erase(nums.begin()+i);
			//每移除一个元素,后面所有元素会前移,下一个元素会移动到当前删除元素位置 
			i--;
		}
	}
	return nums.size();
}

题目2:买卖股票的最佳时机 II

给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。

设计一个算法来计算你所能获取的最大利润。你可以尽可能地完成更多的交易(多次买卖一支股票)。

注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。

示例 1:

输入: [7,1,5,3,6,4]
输出: 7
解释: 在第 2 天(股票价格 = 1)的时候买入,在第 3 天(股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。随后,在第 4 天(股票价格 = 3)的时候买入,在第 5 天(股票价格 = 6)的时候卖出, 这笔交易所能获得利润 = 6-3 = 3 。

示例 2:

输入: [1,2,3,4,5]
输出: 4
解释: 在第 1 天(股票价格 = 1)的时候买入,在第 5 天 (股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。注意你不能在第 1 天和第 2 天接连购买股票,之后再将它们卖出。因为这样属于同时参与了多笔交易,你必须在再次购买前出售掉之前的股票。

示例 3:

输入: [7,6,4,3,1]
输出: 0
解释: 在这种情况下, 没有交易完成, 所以最大利润为 0。

思路:将题目转化模型,本质就是就是算价格上涨期间的利润总和。使用贪心算法,只关心当前利润,只要后一天比前一天的价值高即可进行交易,累加这些利润即可。

int maxProfit(vector<int>& prices) 
{
	int max = 0;
	for(int i=1;i<prices.size();i++)
	{
		int profit = prices[i]-prices[i-1];
		if(profit>0)
		    max += profit;
	} 
	return max;
}

PS:如果看我的思路不太明白为什么这么做,可以看看https://www.jianshu.com/p/d0dbab55b057里面有张图很好解释了为什么这么做。

题目3:旋转数组

给定一个数组,将数组中的元素向右移动 k 个位置,其中 k 是非负数。

示例 1:

输入: [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:

输入: [-1,-100,3,99] 和 k = 2
输出: [3,99,-1,-100]
解释:
向右旋转 1 步: [99,-1,-100,3]
向右旋转 2 步: [3,99,-1,-100]

思路:最初思路是算出真实需要旋转的次数times,之后将从后向前数times个数依次移到数组开头,但是这样做显示超时。之后换了一种相反的思路,将前length-times个数依次移动到数组后面,这样就是一个AC的方法。

/*
超时: 
void rotate(vector<int>& nums,int k) 
{
	if(nums.size()==0)
	    return; 
	int length = nums.size();
	//实际上需要旋转的次数 
	int times = k%length;
	int index = length-1;
	for(int i=0;i<times;i++)
	{
		int num = nums[index];
		nums.pop_back();
		nums.insert(nums.begin(),num);
	}     
}
*/
void rotate(vector<int>& nums,int k) 
{
	if(nums.size()==0)
	    return; 
	int length = nums.size();
	//实际上需要旋转的次数,length次旋转等于没有旋转 
	int times = k%length;
	for(int i=0;i<length-times;i++)
	{
		nums.push_back(nums[0]);
		nums.erase(nums.begin()); 
	}     
}

题目4:存在重复

给定一个整数数组,判断是否存在重复元素。

如果任何值在数组中出现至少两次,函数返回 true。如果数组中每个元素都不相同,则返回 false。

示例 1:

输入: [1,2,3,1]
输出: true

示例 2:

输入: [1,2,3,4]
输出: false

示例 3:

输入: [1,1,1,3,3,4,3,2,4,2]
输出: true

思路:先排序,之后依次将元素和前一个元素比较,若相同则说明有重复元素存在。

bool containsDuplicate(vector<int>& nums) 
{
	if(nums.size()<=1)
	    return false;
	sort(nums.begin(),nums.end());
	for(int i=1;i<nums.size();i++)
		if(nums[i]==nums[i-1])
		    return true;
    return false;    
}

题目5:只出现一次的数字

给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。

说明:

你的算法应该具有线性时间复杂度。 你可以不使用额外空间来实现吗?

示例 1:

输入: [2,2,1]
输出: 1

示例 2:

输入: [4,1,2,1,2]
输出: 4

思路:利用异或去做,相同数字异或为0,数字与0异或为原数字,将所有数字异或一遍剩下的就是单独那个数字。

int singleNumber(vector<int>& nums)
{
	int result = 0;
	for(int i=0;i<nums.size();i++)
		result = result^nums[i];
	return result;	
}

题目6:两个数组的交集 II

给定两个数组,编写一个函数来计算它们的交集。

示例 1:

输入: nums1 = [1,2,2,1], nums2 = [2,2]
输出: [2,2]

示例 2:

输入: nums1 = [4,9,5], nums2 = [9,4,9,8,4]
输出: [4,9]

说明:
输出结果中每个元素出现的次数,应与元素在两个数组中出现的次数一致。
我们可以不考虑输出结果的顺序。

思路:遍历短数组每个元素,并且在较长数组中寻找相对应元素,找到则添加到结果数组中,并且删除长数组中的对应元素,因为题目要求输出结果中每个元素出现的次数,应与元素在两个数组中出现的次数一致,不删除可能会导致长数组中一个元素对应短数组中多个元素的情况。

vector<int> intersect(vector<int>& nums1, vector<int>& nums2) 
{
    vector<int> result;  
    if(nums1.size()>nums2.size())
        swap(nums1,nums2);
    for(int i=0;i<nums1.size();i++)
    {
       for(int j=0;j<nums2.size();j++)
       {
           if(nums1[i]==nums2[j])
           { 
               result.push_back(nums1[i]);
               nums2.erase(nums2.begin()+j);
               break;
           }
       }
                  
    }    
    return result;
}

题目7:加一

给定一个由整数组成的非空数组所表示的非负整数,在该数的基础上加一。

最高位数字存放在数组的首位, 数组中每个元素只存储一个数字。

你可以假设除了整数 0 之外,这个整数不会以零开头。

示例 1:

输入: [1,2,3]
输出: [1,2,4]
解释: 输入数组表示数字 123。

示例 2:

输入: [4,3,2,1]
输出: [4,3,2,2]
解释: 输入数组表示数字 4321。

思路:从低位(数组最后一个元素)向高位做加法运算。

vector<int> plusOne(vector<int>& digits) 
{
	//进位数 
	int temp = 1;
	for(int i=digits.size()-1;i>=0;i--)
	{
		int num = digits[i]+temp;
		digits[i] = num%10;
		temp = num/10;
		if(temp==0)
		    break;
	}
	//要注意最高位的进位 
	if(temp!=0)
	    digits.insert(digits.begin(),temp);
    return digits;    
}

题目8:移动零

给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。

示例:

输入: [0,1,0,3,12]
输出: [1,3,12,0,0]

说明:
必须在原数组上操作,不能拷贝额外的数组。
尽量减少操作次数。

思路:point1指针从数组开头向后寻找为0的数,之后由point2寻找point1之后的第一个非0数,二者交换。当point1超出数组界限(数组中没有0)或者point1后全为0,则交换过程结束。

void moveZeroes(vector<int>& nums) 
{
    if(nums.size()==0)
        return;
    int point1 = 0;    
    int point2 = 0;
    bool flag = true; 
    while(point1<nums.size())
    {
    	flag = true;
    	while(point1<nums.size()&&nums[point1]!=0)
    	    point1++;
    	//找到point1指向的0之后的第一个非0数    
	for(int i=point1+1;i<nums.size();i++)
    	{
    		if(nums[i]!=0)
    		{
    			point2 = i;
    			flag = false;
    			break;
    		}
    	}
    	//如果point1后边全是0了,那么交换过程已经完成 
    	if(flag)
    	    break;
	if(point1<nums.size())
	{
		int temp = nums[point1];
		nums[point1] = nums[point2];
		nums[point2] = temp;
	}	      
    }
}

题目9:两数之和

给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标。

你可以假设每种输入只会对应一个答案。但是,你不能重复利用这个数组中同样的元素。

示例:

给定 nums = [2, 7, 11, 15], target = 9

因为 nums[0] + nums[1] = 2 + 7 = 9
所以返回 [0, 1]

思路:先排序数组,并且在数组左右两边各放一个指针,不停将指针指向数字相加,若大于目标则将右指针向前推一位(因为排过序所以等于缩小二者的和),反之则将左指针向后推一位(增大二者的和)。还有一点很重要的是要记录排序前数组的下标,排序后数组会发生改变。

vector<int> twoSum(vector<int>& nums,int target) 
{
	vector<int> result;
	vector<int> index;
	for(int i=0;i<nums.size();i++)
	    index.push_back(i); 
	//冒泡排序,并调整对应下标 
	for (int i=0;i<nums.size()-1;i++)
	{ 
            for (int j=0;j<nums.size()-1-i;j++) 
	    { 
                if (nums[j]>nums[j+1]) 
		{  
                    int temp1 = nums[j];
                    nums[j] = nums[j+1];
                    nums[j+1] = temp1;
                    //对下标也相应调整 
                    int temp2 = index[j];
                    index[j] = index[j+1];
                    index[j+1] = temp2; 
                }
            }
        }
	int point1 = 0;
	int point2 = nums.size()-1;
	while(point1<point2)
	{
		if(nums[point1]+nums[point2]==target)
		{
			result.push_back(index[point1]);
			result.push_back(index[point2]);
			break;
		}
		if(nums[point1]+nums[point2]>target)
		    point2--;
		if(nums[point1]+nums[point2]<target)
		    point1++;
	}  
	return result;
}

题目10:有效的数独

判断一个 9x9 的数独是否有效。只需要根据以下规则,验证已经填入的数字是否有效即可。
1.数字 1-9 在每一行只能出现一次。
2.数字 1-9 在每一列只能出现一次。
3.数字 1-9 在每一个以粗实线分隔的 3x3 宫内只能出现一次。

数独部分空格内已填入了数字,空白格用 '.' 表示。

示例 1:

输入:
[
  ["5","3",".",".","7",".",".",".","."],
  ["6",".",".","1","9","5",".",".","."],
  [".","9","8",".",".",".",".","6","."],
  ["8",".",".",".","6",".",".",".","3"],
  ["4",".",".","8",".","3",".",".","1"],
  ["7",".",".",".","2",".",".",".","6"],
  [".","6",".",".",".",".","2","8","."],
  [".",".",".","4","1","9",".",".","5"],
  [".",".",".",".","8",".",".","7","9"]
]
输出: true

示例 2:

输入:
[
  ["8","3",".",".","7",".",".",".","."],
  ["6",".",".","1","9","5",".",".","."],
  [".","9","8",".",".",".",".","6","."],
  ["8",".",".",".","6",".",".",".","3"],
  ["4",".",".","8",".","3",".",".","1"],
  ["7",".",".",".","2",".",".",".","6"],
  [".","6",".",".",".",".","2","8","."],
  [".",".",".","4","1","9",".",".","5"],
  [".",".",".",".","8",".",".","7","9"]
]
输出: false
解释: 除了第一行的第一个数字从 5 改为 8 以外,空格内其他数字均与 示例1 相同。
         但由于位于左上角的 3x3 宫内有两个 8 存在, 因此这个数独是无效的。

//判断行 
bool judgeRow(vector<vector<char> >& board,int x,int y)
{
	for(int i=0;i<9;i++)
	{
		if(i!=y&&board[x][y]==board[x][i])
		    return false;
	}
	return true;
}
//判断列 
bool judgeCol(vector<vector<char> >& board,int x,int y)
{
	for(int i=0;i<9;i++)
	{
		if(i!=x&&board[x][y]==board[i][y])
		    return false;
	}
	return true;
} 
//判断九宫格
bool judgeBox(vector<vector<char> >& board,int x,int y)
{
	int row = (x/3)*3;
	int col = (y/3)*3;
	for(int i=row;i<row+3;i++)
	{
		for(int j=col;j<col+3;j++)
		{
			if(i!=x&&j!=y&&board[x][y]==board[i][j])
		            return false;
		}
	}
	return true;
}
bool isValidSudoku(vector<vector<char> >& board) 
{
	for(int i=0;i<board.size();i++)
	{  	
		for(int j=0;j<board[i].size();j++)
		{
			if(board[i][j]=='.')
			    continue;
                   if(!judgeRow(board,i,j)||!judgeCol(board,i,j)||!judgeBox(board,i,j))
                            return false;					     
		}
	}
        return true;        
}

题目11:旋转图像

给定一个 n × n 的二维矩阵表示一个图像。

将图像顺时针旋转 90 度。

说明:

你必须在原地旋转图像,这意味着你需要直接修改输入的二维矩阵。请不要使用另一个矩阵来旋转图像。

示例 1:

给定 matrix =
[
  [1,2,3],
  [4,5,6],
  [7,8,9]
],

原地旋转输入矩阵,使其变为:
[
  [7,4,1],
  [8,5,2],
  [9,6,3]
]

示例 2:

给定 matrix =
[
  [ 5, 1, 9,11],
  [ 2, 4, 8,10],
  [13, 3, 6, 7],
  [15,14,12,16]
],

原地旋转输入矩阵,使其变为:
[
  [15,13, 2, 5],
  [14, 3, 4, 1],
  [12, 6, 8, 9],
  [16, 7,10,11]
]

思路:顺时针旋转分两步:1.将第一行与最后一行交换,倒数第二行与第二行交换,以此类推;2.矩阵转置(行变列,列变行)。

void rotate(vector<vector<int> >& matrix) 
{
	int point1 = 0;
        int point2 = matrix.size()-1; 
	while(point1<point2)
	{
		vector<int> temp = matrix[point1];
		matrix[point1] = matrix[point2];
		matrix[point2] = temp;
		point1++;
		point2--;
	}
	//转置
	for(int i=0;i<matrix.size();i++)
	{
		for(int j=0;j<matrix[0].size();j++)
		{
			if(j<=i)
			    continue;
			int temp = matrix[i][j];
			matrix[i][j] = matrix[j][i];
			matrix[j][i] = temp;   
		}
	} 	    
}

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值