要求
将所有的0,移动到vector的后面
比如; [1,3,0,12,5] -> [1,3,12,5,0]
实现
第一版程序,时间、空间复杂度都是O(n)
1 #include<iostream> 2 #include<vector> 3 4 using namespace std; 5 6 class Solution{ 7 public: 8 void moveZeros(vector<int>& nums){ 9 vector<int> nonZeroElements; 10 for( int i = 0 ; i < nums.size() ; i ++ ) 11 if( nums[i] ) 12 nonZeroElements.push_back(nums[i]); 13 for( int i = 0 ; i < nonZeroElements.size() ; i ++ ) 14 nums[i] = nonZeroElements[i]; 15 for( int i = nonZeroElements.size() ; i < nums.size() ; i ++ ) 16 nums[i] = 0; 17 } 18 }; 19 20 int main(){ 21 int arr[] = {0, 1, 0, 3, 12}; 22 vector<int> vec(arr, arr + sizeof(arr)/sizeof(int) ); 23 Solution().moveZeros( vec ); 24 for( int i = 0 ; i < vec.size() ; i ++ ) 25 cout<<vec[i]<<" "; 26 cout<<endl; 27 }
优化后,空间复杂度O(1)
1 class Solution{ 2 public: 3 // 索引 4 void moveZeros(vector<int>& nums){ 5 int k = 0; 6 // 遍历到第i个元素后,保证[0...i)中所有非0元素 7 // 均按顺序排列在[0...k)中 8 for( int i = 0 ; i < nums.size() ; i ++ ) 9 if( nums[i] ) 10 nums[k++] = nums[i]; 11 // 将[k...n]赋值为0 12 for( int i = k ; i < nums.size() ; i ++ ) 13 nums[i] = 0; 14 } 15 }
利用交换,提高效率
1 void moveZeros1(vector<int>& nums){ 2 int k = 0; 3 for( int i = 0 ; i < nums.size() ; i ++ ) 4 if( nums[i] ) 5 swap( nums[k++] , nums[i] ); 6 }
增加判断,提高非零元素较多时的效率
1 void moveZeros1(vector<int>& nums){ 2 int k = 0; 3 for( int i = 0 ; i < nums.size() ; i ++ ) 4 if( nums[i] ) 5 // 指向同一个元素时不交换 6 if( i != k ) 7 swap( nums[k++] , nums[i] ); 8 else // i == k 9 k++; 10 }
>> 1 3 2 12 0 0
要点
- k始终指向第一个0
- 每次交换后k后移
总结
- 算法设计是一个过程
- 索引非常重要,顺序、选择、循环、交换都是索引的操作而已,每个索引都必须知道自己要去做什么
- 一开始的设计可以是粗粒度的,但不代表不对,只是效率低(时间、空间)
- 发现问题,解决问题,逐步优化
- 不要一开始就陷入测试用例中(先想好怎么操作球,而不去看球上的数字)
- 语法是次要的,思路是主要的,语言的底层都是系统命令
- 设计算法的步骤:思路-->实现-->优化
- 如果看到理解不了思路的复杂算法,不要硬想,有可能是考虑了某种因素的优化版本,先学习容易理解的版本,了解其不足,想想如何在其基础上优化,复杂版本自然就懂了
- 算法就是做一件事的流程,进一步抽象,由三部分组成
- 手:变量,索引,指针
- 物品:数据
- 工具:容器,数组,栈,内存
- 设计一个算法,就是要想清楚怎么用手借助工具去操作物品,即指针如何利用内存操作数据
- 操作的方式是循环(递归),即在n-1步正确为前提,证明n步正确
- 数据结构作为算法的工具,封装了规则,而规则的实现又要依赖于算法
- 数据结构通常以类的形式实现,类中的数据不是真实的数据,而是“虚拟”出来的待操作数据,类中的方法就是算法