C++数组相关技术例题

https://leetcode-cn.com/explore/learn/card/array-and-string/202/conclusion/790/

https://leetcode-cn.com/explore/featured/card/top-interview-questions-easy/1/array/22/

  1. 这里有一些其他类似于数组的数据结构,但具有一些不同的属性:

字符串
哈希表
链表
队列

2. 正如我们所提到的,我们可以调用内置函数来对数组进行排序。但是,理解一些广泛使用的排序算法的原理及其复杂度是很有用的。

  1. 二分查找也是一种重要的技术,用于在排序数组中搜索特定的元素。

  2. 我们在这一章中引入了双指针技巧。想要灵活运用该技技巧是不容易的。这一技巧也可以用来解决:

链表中的慢指针和快指针问题
滑动窗口问题
5. 双指针技巧有时与贪心算法有关,它可以帮助我们设计指针的移动策略。

例题:

旋转数组

给定一个数组,将数组中的元素向右移动 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]
说明:

尽可能想出更多的解决方案,至少有三种不同的方法可以解决这个问题。
要求使用空间复杂度为O(1) 的 原地算法。

思路。时间空间不可兼得。

法一
从0到nums.size()遍历整个数组移动一位,移动n-1次,注意最后一位需移动到nums[0];时间复杂度O(nk),空间复杂度O(n) ,c++超时。

class Solution {
public:
	void rotate(vector<int>& nums, int k) {
			int n = nums.size();
			k %= n;
			for (int j = 1; j <= k; j++)
			{
				int temp = nums[n - 1];
				for (int i = n - 1; i > 0; i--)
					nums[i] = nums[i - 1];
				nums[0] = temp;
			}							
	}
};

法二
利用翻转。
1、首先翻转整个数组。
2、然后翻转前半部分数组,
3、再翻转后半部分数组
/**
* 翻转
* 时间复杂度:O(n)
* 空间复杂度:O(1)
*/
注意reverse 的第二个参数是翻转最后一个位的下一个指针。
void rotate(vector& nums, int k) {
k %= nums.size();
reverse(nums.begin(), nums.end());
reverse(nums.begin(),nums.begin()+k+1);
reverse(nums.begin()+k+1,nums.end());
}

法三
将末尾K位于首部K位交换
第一次交换完毕后,前 k 位数字位置正确,后 n-k 位数字中最后 k 位数字顺序错误,将正确的首部K位从数组中暂时剔除,则末尾K位仍需要一到首部K位,循环继续交换。

	void rotate(vector<int>& nums, int k) {
   		k = k % nums.size();
   		int n = nums.size();
   		for (int i = 0; i < nums.size()&&k!=0 ; n-=k,i+=k,k%=n)
   		{
   			for (int j = 0; j <k; j++)
   				swap(nums[i+j],nums[nums.size()-k+j]);
   		}
   }

杨辉三角 II
给定一个非负索引 k,其中 k ≤ 33,返回杨辉三角的第 k 行。
杨辉三角
在杨辉三角中,每个数是它左上方和右上方的数的和。

思路:区别仅在于在一个数组上操作,将单前位于前一位相加。

class Solution {
public:
	vector<int> getRow(int rowIndex) {
		vector<int>result;
		for (int i = 0; i <= rowIndex; ++i)
		{
			result.push_back(1);
			for (int j = i - 1; j > 0; --j)
				result[j] += result[j - 1];
		}
		return result;
	}
};

翻转字符串里的单词

给定一个字符串,逐个翻转字符串中的每个单词。

示例 1:

输入: “the sky is blue”
输出: “blue is sky the”
示例 2:

输入: " hello world! "
输出: “world! hello”
解释: 输入字符串可以在前面或者后面包含多余的空格,但是反转后的字符不能包括。
示例 3:

输入: “a good example”
输出: “example good a”
解释: 如果两个单词间有多余的空格,将反转后单词间的空格减少到只含一个。

说明:

无空格字符构成一个单词。
输入字符串可以在前面或者后面包含多余的空格,但是反转后的字符不能包括。
如果两个单词间有多余的空格,将反转后单词间的空格减少到只含一个。

法一 运用栈来存取结果,遍历string,遇到\t则压入栈中,注意开头结尾是空格和连续空格的情况。

class Solution {
public:
	string reverseWords(string s) {
		string res;
		stack<string> box;
		int b=0;
		if (!s.empty())
		{
			if (s[0] != ' ')
				b = 0;
			for (int i = 1; i < s.length() ; i++)
			{
				if (s[i] == ' ' && s[i - 1] != ' ')
					box.push(s.substr(b, i - b));
				else if (s[i] != ' ' && s[i - 1] == ' ')
					b = i;
			}
			if (s[s.length() - 1] != ' ')
				box.push(s.substr(b, s.length() - b));

			while (!box.empty())
			{
				res += box.top();
				box.pop();
				if (!box.empty())
					res += ' ';
			}
		}
		return res;					
	}
};

法二

可用find_first_not_of " "更简便)
find 函数除此之外还有 find_last_not_of/find_first_not_of/find_first_of/find_last_of

反转字符串中的单词 III

给定一个字符串,你需要反转字符串中每个单词的字符顺序,同时仍保留空格和单词的初始顺序。

示例 1:

输入: “Let’s take LeetCode contest”
输出: “s’teL ekat edoCteeL tsetnoc”
注意:在字符串中,每个单词由单个空格分隔,并且字符串中不会有任何额外的空格。


class Solution
{
public:
    string reverseWords(string s)
    {
        if (s.size() == 0){//如果s是空字符串,直接返回
            return s;
        }
        int front = 0, back = 0;//front为要反转的单词的首字母,back为要反转的单词的末字母的下一位
        for (int i = 0; i < s.size() - 1; i++)
        {
            if (s[i] != ' '){
                back++;
            }
            else{
                reverse(s.begin() + front, s.begin() + back);
                front = back + 1; //当前back指向空格,所以front要从空格的下一个即下一个单词的首字母开始
                back = front;//front跟back同一线
            }
        } //此时最后一部分还没有反转,因为s的末尾不是空格,所以此时back应等于最后一个单词的末字母
        back++;
        reverse(s.begin() + front, s.begin() + back); //reverse的最后一个参数是要反转的结尾的下一位,back指向s的最后一位,所以要+1
        return s;
    }
};

删除排序数组中的重复项

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

不要使用额外的数组空间,你必须在原地修改输入数组并在使用 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。

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

为什么返回数值是整数,但输出的答案是数组呢?

请注意,输入数组是以“引用”方式传递的,这意味着在函数里修改输入数组对于调用者是可见的。

你可以想象内部操作如下:

// nums 是以“引用”方式传递的。也就是说,不对实参做任何拷贝
int len = removeDuplicates(nums);

// 在函数里修改输入数组对于调用者是可见的。
// 根据你的函数返回的长度, 它会打印出数组中该长度范围内的所有元素。
for (int i = 0; i < len; i++) {
print(nums[i]);
}

法一
运用一个map来记录元素的依次新位置,对于重复元素不记录。按照map来放置新元素。

class Solution {
public:
	int removeDuplicates(vector<int>& nums) {
		map<int,int> mapping;
		int j = 0;
		for (int i = 0; i < nums.size(); i++)
			if (mapping.find(nums[i]) == mapping.end())
				mapping[nums[i]] = j++;

		for (auto iter=mapping.begin();iter!=mapping.end(); iter++)
			nums[iter->second]=iter->first;
		return 	 mapping.size();
	}
};

法二
双指针法,一个指针指向更改元素,另一个元素向前遍历。

class Solution {
public:
int removeDuplicates(vector& nums) {
int s = 0;
if (nums.size() > 0)
{
s = 1;
for (int i = 1; i < nums.size(); i++)
if (nums[i] != nums[i - 1])
nums[s++] = nums[i];
}

	return s;
}		

};

移动零

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

示例:

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

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

法一
两个指针,一个指针寻找0的位置,一个指针遍历数组寻找非0,当第一个0的位置浅语非0时,则交换位置,继续寻找下一个0的位置。注意0的位置必须不超过非0.
仅需遍历两次数组,且非0数组都是一次到位,是最简便的解法。

class Solution {
public:
	void moveZeroes(vector<int>& nums) {
		int s=-1;  //慢指针,仅指向0,s指向快指针前的第一个的零。
		for (int j = 0; j < nums.size(); j++)
		{

			if (j == nums.size() - 1)
			{
				s = -1;
				break;
			}
			if (nums[j] == 0)
			{
				s = j;
				break;
			}
		}
		for (int i = 0; i < nums.size(); i++)
			if (nums[i] != 0 && s >= 0 &&s<i )
			{		
				swap(nums[i], nums[s]);
				for (int j = s; j < nums.size(); j++)
				{	
					
					if (j == nums.size() - 1)
					{
						s = -1;
					}

					if (nums[j] == 0)
					{
						s = j;
						break;
					}									
				}				
			}	    		
	}
};

删除链表的倒数第N个节点

当删除掉第一个元素时,dummy->next与head不相同,因此最后应该返回dummy->next

给定一个链表,删除链表的倒数第 n 个节点,并且返回链表的头结点。

示例:

给定一个链表: 1->2->3->4->5, 和 n = 2.

当删除了倒数第二个节点后,链表变为 1->2->3->5.
说明:

给定的 n 保证是有效的。

进阶:

你能尝试使用一趟扫描实现吗?

思路一
快慢指针,快指针先走N步。:
添加dummy 结点以解决边界问题。如
1、结点数为1
2、删除节点是头节点
因为头节点可能被删除,因此返回的是dummy->next

class Solution {
public:
	ListNode* removeNthFromEnd(ListNode* head, int n) {
		ListNode *dummy = new ListNode(-1);
		dummy->next=head;
		ListNode* q=dummy;
		ListNode* s=dummy;
			for (int i = 0; i < n; i++)
				s = s->next;

			while (s->next != NULL)
			{
				s = s->next;
				q = q->next;
			}
			q->next = q->next->next;		
		return dummy->next;
	}
};

思路二
扫描时维护栈或数组保存结点。数组或栈类型为*ListNode

ListNode* removeNthFromEnd(ListNode* head, int n) {
		ListNode* dummy = new ListNode(-1);
		dummy->next = head;
		ListNode* s = dummy;
		vector<ListNode*> nodelist;
		nodelist.push_back(s);
		while (s->next != NULL)
		{
			s = s->next;
			nodelist.push_back(s);
		}
		nodelist[nodelist.size() - n - 1]->next = nodelist[nodelist.size() - n - 1]->next->next;
		return dummy->next;

删除链表的倒数第N个节点

给定两个大小为 m 和 n 的有序数组 nums1 和 nums2。

请你找出这两个有序数组的中位数,并且要求算法的时间复杂度为 O(log(m + n))。

你可以假设 nums1 和 nums2 不会同时为空。

示例 1:

nums1 = [1, 3]
nums2 = [2]

则中位数是 2.0
示例 2:

nums1 = [1, 2]
nums2 = [3, 4]

则中位数是 (2 + 3)/2 = 2.5

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/median-of-two-sorted-arrays
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

这道题让我们求两个有序数组的中位数,而且限制了时间复杂度为O(log (m+n)),看到这个时间复杂度,自然而然的想到了应该使用二分查找法来求解。那么回顾一下中位数的定义,如果某个有序数组长度是奇数,那么其中位数就是最中间那个,如果是偶数,那么就是最中间两个数字的平均值。这里对于两个有序数组也是一样的,假设两个有序数组的长度分别为m和n,由于两个数组长度之和 m+n 的奇偶不确定,因此需要分情况来讨论,对于奇数的情况,直接找到最中间的数即可,偶数的话需要求最中间两个数的平均值。为了简化代码,不分情况讨论,我们使用一个小trick,我们分别找第 (m+n+1) / 2 个,和 (m+n+2) / 2 个,然后求其平均值即可,这对奇偶数均适用。加入 m+n 为奇数的话,那么其实 (m+n+1) / 2 和 (m+n+2) / 2 的值相等,相当于两个相同的数字相加再除以2,还是其本身。

这里我们需要定义一个函数来在两个有序数组中找到第K个元素,下面重点来看如何实现找到第K个元素。首先,为了避免产生新的数组从而增加时间复杂度,我们使用两个变量i和j分别来标记数组nums1和nums2的起始位置。然后来处理一些边界问题,比如当某一个数组的起始位置大于等于其数组长度时,说明其所有数字均已经被淘汰了,相当于一个空数组了,那么实际上就变成了在另一个数组中找数字,直接就可以找出来了。还有就是如果K=1的话,那么我们只要比较nums1和nums2的起始位置i和j上的数字就可以了。难点就在于一般的情况怎么处理?因为我们需要在两个有序数组中找到第K个元素,为了加快搜索的速度,我们要使用二分法,对K二分,意思是我们需要分别在nums1和nums2中查找第K/2个元素,注意这里由于两个数组的长度不定,所以有可能某个数组没有第K/2个数字,所以我们需要先检查一下,数组中到底存不存在第K/2个数字,如果存在就取出来,否则就赋值上一个整型最大值。如果某个数组没有第K/2个数字,那么我们就淘汰另一个数字的前K/2个数字即可。有没有可能两个数组都不存在第K/2个数字呢,这道题里是不可能的,因为我们的K不是任意给的,而是给的m+n的中间值,所以必定至少会有一个数组是存在第K/2个数字的。最后就是二分法的核心啦,比较这两个数组的第K/2小的数字midVal1和midVal2的大小,如果第一个数组的第K/2个数字小的话,那么说明我们要找的数字肯定不在nums1中的前K/2个数字,所以我们可以将其淘汰,将nums1的起始位置向后移动K/2个,并且此时的K也自减去K/2,调用递归。反之,我们淘汰nums2中的前K/2个数字,并将nums2的起始位置向后移动K/2个,并且此时的K也自减去K/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。
在这里插入图片描述
如果我们分析图表,那么我们的兴趣点是连续的峰和谷。

关键是我们需要考虑到紧跟谷的每一个峰值以最大化利润。如果我们试图跳过其中一个峰值来获取更多利润,那么我们最终将失去其中一笔交易中获得的利润,从而导致总利润的降低,即关键在于连续交易

法一:注意实现时可以一边更新profit,一边寻找峰值,减少遍历次数

class Solution {
public:
	int maxProfit(vector<int>& prices) {
		int profit = 0;
		if (!prices.empty()) {
		int top = prices[0];
		int val = prices[0];
		for (int i = 1; i < prices.size(); i++)
		{
			if (prices[i] < top)
			{
				val = prices[i];
				top = val;
			}
			if (prices[i] > top)
			{
				profit += prices[i] - top;
				top = prices[i];
			}
		}
	}
		return profit;
	}	
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值