剑指offer / Leetcode算法题

一.位运算总结规律

1.与运算

1)判断num的奇偶:

num & 1 = 1 (num奇数)
num & 1 = 0 (num偶数)

2)(num-1)& num
将num的最后一位1消去变成0(可用于统计num有多少个1)

2.异或运算

1)偶数个num相异或,结果为0;
奇数个num相异或,结果为num。
用公式代替:

num ^ num = 0;
num ^ b ^ num = b;

2)交换两数(不使用中间变量)

a = a^b;
b = a^b;
a = a^b;

另外,还可以用加减法/乘除法(容易出现溢出问题)

a = a+b;
b = a-b;
a = a-b;

3.取反运算

1)一个数与其相反的数相加,结果为-1

a + ~a = -1

T1:二进制中1的个数

题目:输入一个整数,输出该数二进制表示中1的个数。其中负数用补码表示。

概念:
正数的补码 = 原码
负数的补码 = 原码按位取反(除最高位)+ 1
正数右移,最高位补0;负数右移,最高位补1;
思路:
方法1:将这个数“按位与”1,再将1左移。之所以不将该数右移,是因为要考虑到负数,负数右移,高位补1,改变了1的数量。
方法2:假设某数为n,那么n&(n-1)得到的效果就是将该数的低位中的1变0。如此循环,n中有多少个1,就可以变换多少次
方法2比方法1省时,方法1必须移动32次,而方法2只要没有1了就停止了(改变了原数n)。
方法3:(python)用split统计字符串中的某个字符个数
print(len(str.split(‘x’))-1)

方法1:

int  NumberOf1(int n) 
{
         int num = 1;
         int count = 0;
         while(num != 0)
         {
             if((num & n) != 0)
                 count++;
             num <<= 1;
         }
         return count;
 }

方法2:

int  NumberOf1(int n) 
{
         int count = 0;
         while(n)
         {
             n &= n-1;
             count++;
         }
         return count;
     }

T2:数值的整数次方

题目:给定一个double类型的浮点数base和int类型的整数exponent。求base的exponent次方。保证base和exponent不同时为0

寻常的for/while/递归做法,时间复杂度O(n),循环n次,乘n次。当n极大时,很明显上面的方法就很慢了,还可能超时。所以采用其它的算法。
方法:二分幂。时间复杂度为O(logn)。为了求2^ 8,可以先求2^ 4;为了求2^ 4,可以先求2^2,如此循环直至n=1。
小技巧:
1)判断奇偶。num % 2 == 1可以用位运算代替。num & 1 == 1 -> 奇数;num & 1 == 0->偶数。
2)减半。power=power/2 可以用位运算代替。
power >>= 1;

float pow(int a, int n)//二分幂,定义成float类型
    {
        if (n == 0)
            return 1;
        if (n == 1)
            return a;
        int mut = pow(a, n / 2);
        mut *= mut;
        if (n % 2 == 1)  
            mut *= a;
        return mut;
    }
double Power(double base, int exponent) 
   {
    	if (exponent >= 0)  //幂为正数
  	    	return pow(base, exponent);
        return 1 / pow(base, -exponent); //幂为负数,为了避免结果为0,上面的函数定义成float
   }

T3:只出现一次的数字

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

思路:奇数个相同的数进行异或,结果为本身,偶数个进行异或结果为0.
0 ^ num = num;
num ^ num = 0;

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

T4:不用加减乘除做加法

题目描述
写一个函数,求两个整数之和,要求在函数体内不得使用+、-、*、/四则运算符号。

两个数异或:相当于每一位相加,而不考虑进位;
两个数相与,并左移一位:相当于求得进位;

int Add(int num1, int num2)
    {
        while (num1)
		{
			int a = (num1 & num2) << 1;
			int b = num1 ^num2;
			num1 = a;
			num2 = b;
		}
		return num2;
    }

二. 利用指针移动(链表思想)

将一串有规律的数字当成链表,用指针pre+next来控制移动。
将链表当作一个窗口,将双指针当作一把尺子,从前往后移动。

T1. 和为S的连续正数序列

题目描述:小明很喜欢数学,有一天他在做数学作业时,要求计算出9~16的和,他马上就写出了正确答案是100。但是他并不满足于此,他在想究竟有多少种连续的正数序列的和为100(至少包括两个数)。没多久,他就得到另一组连续正数和为100的序列:18,19,20,21,22。现在把问题交给你,你能不能也很快的找出所有和为S的连续正数序列。

方法1:
思路:既然是连续的正数序列,那么将这一串数字想象成链表,并用两个指针pre,next线性移动(也就是剑指offer里大神说的“双指针+窗口”)。

在这里插入图片描述

vector<vector<int> > FindContinuousSequence2(int sum)
 {
  vector<vector<int> > A;
  int pre = 1, next = 2;  //pre和next从1和2开始移动
  while (pre < next)
  {
       int curSum = (pre + next)*(next - pre + 1) / 2;
       if (curSum < sum) next++;   //小于
       else if (curSum == sum)     //等于
   	{
	    vector<int>a;
	    for (int i = pre; i <= next; i++)
   	     {     
     		a.push_back(i);
    	     }
            pre++;
            A.push_back(a);
        }
       else if (curSum > sum) pre++;   //大于
  }
  return A;
 }

方法2:
思路:我的方法,找了规律,比较牵强。从组成个数i考虑,i为奇数/偶数,i为奇数的话,中间值mid=sum/2,那么左右两边的数也好确定;i为偶数,情况多一些。
等差数列的求和公式,充分不必要条件(first+last)* i / 2 = sum,可以得出i的范围,减少时间复杂度
i是整数个数,若存在连续i个整数相加为sum,必定满足2*sum%i==0,不满足就不用考虑这个i了。
i之所以从3开始,不从2开始,是因为sum=奇数时,需要考虑i=2的这种情况,而sum=偶数时,不存在两个连续正数相加为sum,不必讨论,所以将i=2单独放到最后讨论。

 vector<vector<int> > FindContinuousSequence(int sum) {
   vector<vector<int> > A;
   for (int i = sqrt(2 * sum); i >= 3; i--)    //充分不必要,i有范围
   {  
      if (2 * sum % i == 0)
       {
         int mid = sum / i;
    	 vector<int> a;
    	 int j;
    	 for (j = (i-1) / 2; j >= 1; j--)  //传入mid左边的数,注意顺序
    	 {
             a.push_back(mid - j);   
    	 }
         a.push_back(mid);      //传入mid
    	 for (j = 1; j <= (i-1) / 2; j++)    //传入mid右边的数,
    	 {
     	     a.push_back(mid + j);
   	 }
     if (sum % i != 0)     // 此时i为偶数个,比奇数个多添加一个元素。
    	{
     	     a.push_back(mid + j);
    	}
     if (a.size() != i) continue;  //用于排除“sum%i ==0时,可能存在,也可能不存在”中的不存在的情况 例如i=4,sum=100,求出来的连续整数24,25,26,但不符合实际情况,i和a.size不等 
     A.push_back(a);
   }
  }
  if (sum / 2 >=1 && sum % 2 == 1)     //讨论i=2的这种情况,i=2时,有且只有奇数sum存在这种情况,偶数sum中不存在两个连续整数相加的这种组合。并且排除sum=1 
  {
     int mid = sum / 2;
     vector<int> a;
     a.push_back(mid);
     a.push_back(mid + 1);
     A.push_back(a);
  }
  return A;
 }

T2. 和为S的两个数字

题目描述:输入一个递增排序的数组和一个数字S,在数组中查找两个数,使得他们的和正好是S,如果有多对数字的和等于S,输出两个数的乘积最小的。

思路:同T1的思路,双指针+夹逼准则
题目要求输出两个数乘积最小的,由于数组递增,pre是从最小的数开始遍历,一旦找到sum=pre+next,即可退出,不用继续查找所有满足“和等于S”的pre和cur。
从下图可知,当sum=0,找出了三对,第一对-16是三队中结果最小的,不管是用正负数结合还是纯正数/纯负数,外层的积都要小于内层的积,所以只要找到第一对就可以退出,不用继续找其它满足条件的对数

在这里插入图片描述

vector<int> FindNumbersWithSum(vector<int> array,int sum) {
     int len = array.size();
     int pre = 0, next = len-1;   //注意此处,next从尾开始,pre从头
     vector<int> a;
     while (pre < next)
  	{
	   int curSum = array[pre] + array[next];
	   if (curSum < sum) pre++;
	   if (curSum == sum)
    	    {
	      a.push_back(array[pre]);
	      a.push_back(array[next]);
	      return a;
            }
   	   if (curSum > sum)
            {
    	      next--;
   	    }
  	}
      return a;
    }

我的方法,与夹逼方法不同的是,我的方法中的pre和next是从头开始,不是从两边夹击。

vector<int> FindNumbersWithSum(vector<int> array, int sum) {
  int len = array.size();
  int pre = 0, next = 1;    //不同处1
  vector<int> a;
  while (pre < next && next <= len)
  {
     int curSum = array[pre] + array[next];
     if (curSum < sum) next++;    //不同处2
     if (curSum == sum)
     { 
    	a.push_back(array[pre]);
    	a.push_back(array[next]);
    	return a;
     }
     if (curSum > sum)    //不同处3
     {
    	pre++;               
        next = pre + 1;
        if ((sum - array[pre]) < array[next]) break;
     }
   }
  return a;
 }

T3. 链表中倒数第k个节点

题目描述:输入一个链表,输出该链表中倒数第k个节点。为了符合大多数人的习惯,本题从1开始计数,即链表的尾节点是倒数第1个节点。例如,一个链表有6个节点,从头节点开始,它们的值依次是1、2、3、4、5、6。这个链表的倒数第3个节点是值为4的节点。

思路: 如果用传统的链表的做法也可以,用双指针做法会更有效。

  • 方法1(传统思想):先从头遍历得出链表长度length,再从头开始遍历,直到第length-k个节点;或者将链表节点push到stack,再从stack依次弹出第k个节点(若用python,push完后,采用切片,直接return stack[-k]即可,不用依次弹出)。
  • 方法2:递归。递归到head=NULL,再开始计数,一直到count=k,就可以将当前的node存入临时变量中。
  • 方法3:双指针(推荐)。将这个链表看作是一个长方形窗口,将双指针看作是一把长度为k的尺子,尺子从头开始往后移动,当尺子的前端滑到窗口的低端,那么尺子的后端所指向的位置就是k所在的位置。看图如下:

在这里插入图片描述
方法1:代码略。
方法2:递归

class Solution {
public:
    ListNode* temp;   //临时变量,最终的返回值
	int countKth(ListNode* head, int k)
	{
		if (head == NULL) return 0;
		int count = countKth(head->next, k) + 1;
		if (count == k)
			temp = head;
		return count;
	}
	ListNode* getKthFromEnd2(ListNode* head, int k) {
		if (k < 1) return NULL;
		countKth(head, k);   //不需要接收返回值
		return temp;
	}
};

方法3:双指针+窗口

ListNode* getKthFromEnd3(ListNode* head, int k) {
		if (k<1) return NULL;
		ListNode* pre = head;
		ListNode* cur = head;      //此时还未形成一把尺子,因为起点一样,尺子长度为0
		int copy = k;
		while (copy > 1)
		{
			cur = cur->next;
			copy--;
		}                      //形成一把尺子,长度为k
		while (cur->next)      //形成尺子之后,开始移动尺子,直到这把尺子前端移动到窗口最右端,停止。
		{
			cur = cur->next;
			pre = pre->next;
		}
		return pre;
	}

T4. 链表相交

题目描述:输入两个链表,找到它们的第一个公共交点。
示例1:
在这里插入图片描述
在这里插入图片描述

注意:判断两个链表是否相交,是基于结点的引用,而不是基于结点的值。也就是说,如果一个链表的第K个结点与另一个链表的第J个结点是同一个结点(引用相同),那么它们相交。注意上面的第二张图,公共交点是8,不是1,因为两个链表中的结点1的地址不同,虽然从表面上看,val相同,链接的下一个结点也是8。所以在代码中,判断条件是 if(curA == curB),而不是if(curA->val == curB->val).
思路:方法1:见下图
在这里插入图片描述
方法2:双指针。
将两个链表头尾串联,a,b分别表示链表A和链表B不相交的结点长度,n表示它们相交部分的结点长度,相交的部分用蓝色阴影表示。在图中,假设B长,其实双指针移动与它们的长度并没有什么关系)
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

代码:

ListNode *getIntersectionNode2(ListNode *headA, ListNode *headB)
 {
    ListNode* curA = headA;
    ListNode* curB = headB;
    while (curA != curB)
     {
   	curA = curA == NULL ? headB : curA->next;
   	curB = curB == NULL ? headA : curB->next;
     }
    return curA;
 }

T5. 链表的中间结点

题目描述:给定一个带有头结点 head 的非空单链表,返回链表的中间结点。如果有两个中间结点,则返回第二个中间结点。
示例1 :输入:[1,2,3,4,5]
输出 :返回结点 3
示例2 :输入:[1,2,3,4,5,6]
输出 :返回结点 4 (由于该列表有两个中间结点,值分别为 3 和 4,我们返回第二个结点。)

思路:双指针,慢指针走1步,快指针走两步,快指针走到底后,慢指针恰好到链表中间。

代码

ListNode* middleNode(ListNode* head) {
    ListNode* pre = head;
    ListNode* cur = head;
    while (cur && cur->next)
  	{
   	   pre = pre->next;
           cur = cur->next->next;
  	}
    return pre;
 }

T6. 回文链表

题目描述:编写一个函数,检查输入的链表是否是回文的。
示例 1:
输入:1->2
输出: false
示例 2:
输入:1->2->2->1
输出: true

思路:其实就是判断该链表是否对称(以中间结点),差不多这个意思吧。至于长度为偶数和长度为奇数是否有影响,其实不影响的。例如1->2->3->2->1,将pre定位到3后,反转3->2->1成1->2->3,此时比较前半部分以head开头的1->2和后半部分以pre开头的1->2->3,while(head && pre),自然是不会比较到3的。当长度为偶数时,更容易比较,不做讨论。
方法1:反转链表的后半部分,然后将链表的前半部分和后半部分一一进行对比是否val相等。用到的知识点基本都是其它链表算法题的综合,知识点1:用双指针定位到链表的中间结点;知识点2:反转链表(我用的是反转链表的第二种方法,详见 "目录四. 链表” )

代码:

bool isPalindrome(ListNode* head) {
    if (head == NULL) return true;  //空链表为回文链表
  //定位到中间结点
    ListNode* cur = head;
    ListNode* pre = head;
    while (cur && cur->next)
     {
   	pre = pre->next;   //走一步
        cur = cur->next->next;   //走两步
     }
    //反转中间结点的后面部分
    cur = pre;
    ListNode* next = pre->next;
    while (next)
     {
   	cur->next = next->next;
   	next->next = pre;
   	pre = next;
   	next = cur->next;
     }
   //开始对比以head开头的前半部分后以pre开头的后半部分
    while (head && pre)
     {
   	if (head->val == pre->val)
   	{
    	    head = head->next;
    	    pre = pre->next;
   	}
   	else
    	    return false;
     }
  return true;
 }

方法2:引入堆栈,简单。将所有的值存入栈中,由于stack本身存在先入后出的规律,所以存入stack顶端的结点是链表中最后面的结点,从stack顶端开始至下,与链表从前往后一一比较。

代码:

bool isPalindrome2(ListNode* head)
 {
  	stack<int>stack;
  	ListNode* cur = head;
  	while (cur)
  	{
   		stack.push(cur->val);
   		cur = cur->next;
  	}
  	while (!stack.empty())
  	{
   		int temp = stack.top();
   		if (head->val == temp)
   		{
    			stack.pop();
    			head = head->next;
   		}
   		else
    			return false;
  	}
  return true;
 }

三. 字符串

T1. 字符流中第一个不重复的字符

题目描述
请实现一个函数用来找出字符流中第一个只出现一次的字符。例如,当从字符流中只读出前两个字符"go"时,第一个只出现一次的字符是"g"。当从该字符流中读出前六个字符“google"时,第一个只出现一次的字符是"l"。

思路:利用哈希表将字符本身作为下标索引,不用数字‘0,1,2…’ 做下标索引。

class Solution
{
public:
  //Insert one char from stringstream
    void Insert(char ch)
    {
        str += ch;    //字符串拼接
        hash[ch]++;   //以字符本身为索引
    }
  //return the first appearence once char in current stringstream
    char FirstAppearingOnce()
    {
        for(int i=0;i<str.size();i++)
        {
            if(hash[str[i]] == 1)
                return str[i];   //此时返回的一定是只出现一次的字符
        }
        return '#';
    }
private:    
    string str;
    char hash[256] = {0};
};

四. 链表

T1. 反转链表

题目描述:反转一个单链表。
示例:
输入: 1->2->3->4->5->NULL
输出: 5->4->3->2->1->NULL

方法1:三个指针,改变每个节点的指向,图示如下:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

代码:

ListNode* reverseList(ListNode* head) {
		ListNode* pre = NULL;
		ListNode* cur = head; 
		ListNode* next = head;
		while (cur)
		{
			next = next->next;     //步骤1
			cur->next = pre;       //2
			pre = cur;             //3
			cur = next;            //4
		}
		return pre;
	}

方法2:原本链表顺序为:1->2->3->4,现在将2挪到1之前,变成2->1->3->4,该步骤图示如下,然后再修改,将3挪到2之前,变成3->2->1->4,再修改,变成4->3->2->1。
在这里插入图片描述
在这里插入图片描述

代码:

ListNode* reverseList2(ListNode* head) {
		if(head == NULL) return head;
		ListNode* cur = head;
		ListNode* next = head->next;   //当head为NULL,如果没有上面的if判断语句,执行到这步会报错,因为head->next不存在。
		ListNode* pre = head;
		while (next)
		{
			cur->next = next->next;   //步骤1
			next->next = pre;         //2
			pre = next;				  //3
			next = cur->next;		  //4
		}
		return pre;
	}

T2. 删除链表中重复的结点

分三类题型:
题型一:删除有序链表中重复的结点,重复结点只保留一个

题目描述:给定一个排序链表,删除所有重复的元素,使得每个元素只出现一次。
示例 :输入: 1->1->2
输出: 1->2

代码:

ListNode* removeDuplicateNodes(ListNode* head) {
    if (head == NULL || head->next == NULL) return head;
    ListNode* cur = head;
    while (cur->next)
  	{
   	   if (cur->val == cur->next->val)
            {
    		cur->next = cur->next->next;
   	    }
   	   else
   	    {
    	        cur = cur->next;
   	    }
        }
    return head;
 }

题型二:删除有序链表中重复的结点,重复结点一个不留

题目描述
在一个排序的链表中,存在重复的结点,请删除该链表中重复的结点,重复的结点不保留,返回链表头指针。
示例:1->2->3->3->4->4->5
输出: 1->2->5

思路:重新生成头节点,是为了避免出现1->1->2->3这种第一个结点就出现重复,需要删除的情况,如果删除了,那么return
pHead时就会出现丢失的情况。代码图示如下:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

代码:

ListNode* deleteDuplication(ListNode* pHead)
   {
  	if (pHead == NULL || pHead->next == NULL) return pHead;
	ListNode* newHead = new ListNode(0);    //重新生成一个头节点,val为0
	newHead->next = pHead;
	ListNode* pre = newHead;
	ListNode* cur = newHead->next;
	while (cur)
	  {
   	     if (cur->next && cur->val == cur->next->val)
   	      {
    		    while (cur->next && cur->val == cur->next->val)
    			{
		     	  cur = cur->next;    // 定位重复的结点中的最后一个
		        }
    		    pre->next = cur->next;
    		    cur = cur->next;
   	      }
   	     else
	      {
	            pre = pre->next;
	            cur = cur->next;
	      }
	  }
     return newHead->next;
 }

题型三:删除未排序链表中的重复结点,重复出现的结点只保留一个

题目描述
移除未排序链表中的重复节点。保留最开始出现的节点。
示例::1-> 2-> 3-> 3-> 2-> 1
输出: 1->2->3
提示:链表长度在[0, 20000]范围内。链表元素在[0, 20000]范围内。

思路:
方法1:没有根据提示采用的方法,不具有特殊性,双层for循环的思路,代码中用两个while替代双for,时间复杂度为O(n^2),费时。
方法2:采用缓冲区,创建一个长度为20000的hash,具有特殊性。用hash的下标标记每一个结点的val出现的次数,对于重复出现的结点,跳过,链表的next不再链接这个结点。时间复杂度为O(n).。hash简单使用如图所示:
在这里插入图片描述

代码:方法1:

ListNode* removeDuplicateNodes2(ListNode* head) {
  	ListNode* pre = head;
  	ListNode* cur = head;
 	while (pre)
  	{
   	    cur = pre;
   	    while (cur->next)
   	    {
    		if (pre->val == cur->next->val)
       		    cur->next = cur->next->next; 
    	        else
     		    cur = cur->next;
   	    }
   	    pre = pre->next;
  	}
        return head;
 }
 

方法2:

ListNode* removeDuplicateNodes3(ListNode* head)
 {
    if (head == NULL) return head;
    int hash[20001] = { 0 };      //因为链表长度在[0, 20000]范围内
    ListNode* cur = head;
    hash[cur->val] = 1;
    while (cur->next)
  	{
   	   if (hash[cur->next->val] == 0)
   	    {
      		hash[cur->next->val]++;
    		cur = cur->next;
   	    }
   	  else
    		cur->next = cur->next->next; 
  	}
    return head;
 }

T3. 删除链表指定结点

题目描述:给定单向链表的头指针和一个要删除的节点的值,定义一个函数删除该节点。返回删除后的链表的头节点。
示例 :4->5->1->9, val = 5
输出:4->1->9
说明
题目保证链表中节点的值互不相同
若使用 C 或 C++ 语言,你不需要 free 或 delete 被删除的节点

注意:如果链表为 4->5->1->9, val = 4,第一个结点就是要删除的,不能直接删除,需要构造一个头节点newHead,
newHead的next 指向原链表的 head,见代码。由于链表结点的val互不相等,那么只要满足pre->next->val == val的条件,就可以return链表**

代码:

ListNode* deleteNode(ListNode* head, int val) {
  	ListNode* newHead = new ListNode(0);   //构造头节点只是为了避免第一个结点就是要删除的结点
  	newHead->next = head;
  	ListNode* pre = newHead;
  	while (pre->next)
  	{
   	    if (pre->next->val == val)
   	     {
    		pre->next = pre->next->next;
    		return newHead->next;
   	     }
   	    else
    		pre = pre->next;
  	}
  	return NULL;
   }

T4. 删除链表元素

题目描述:删除链表中等于给定值 val 的所有节点。
示例:
输入: 1->2->6->3->4->5->6, val = 6
输出: 1->2->3->4->5

思路:T4与T3类似,也是构造表头,以防第一个结点就是要删除的结点。利用双指针/单指针均可,使用单指针的话,可以参考T3。

ListNode* removeElements(ListNode* head, int val) {
		ListNode* newhead = new ListNode(0);
		newhead->next = head;
		ListNode* pre = newhead;
		while (head)
		{
			if (pre->next->val == val)
			{
				pre->next = head->next;
			}
			else
			{
				pre = head;
			}
			head = head->next;
		}
		return newhead->next;
	}

T5. 合并有序链表

题目描述:将两个有序链表合并为一个新的有序链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
示例
输入:1->2->4, 1->3->4
输出:1->1->2->3->4->4

思路:三指针,cur1和cur2边移动边比较,这两个指针仅仅做大小上的比较,各自在各自的链表上从前往后移动,谁小,谁就往自己的那个链表尾端移动,大的那个先不动。而pre负责指向,串联起两个链表,在两个链表间来回移动。

ListNode* mergeTwoLists1(ListNode* l1, ListNode* l2) {
		if (l1 == NULL && l2 == NULL) return NULL;
		if (l1 == NULL && l2) return l2;
		if (l1 && l2 == NULL) return l1;
		ListNode* head = new ListNode(0);
		ListNode* cur1 = l1;
		ListNode* cur2 = l2;
		ListNode* pre = head;
		if (cur1->val <= cur2->val)
			head->next = cur1;
		else
			head->next = cur2;
		while (cur1 && cur2)
		{	
			cur1->val <= cur2->val ? (pre->next = cur1, cur1 = cur1->next) : (pre->next = cur2,cur2 = cur2->next);
			pre = pre->next;
		}
		if (cur1)
			pre->next = cur1;
		if (cur2)
			pre->next = cur2;
		return head->next;
	}

五. 数组

T1. 旋转数组的最小数字

题目描述
把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。输入一个非递减排序的数组的一个旋转,输出旋转数组的最小元素。例如数组{3,4,5,1,2}为{1,2,3,4,5}的一个旋转,该数组的最小值为1。
NOTE:给出的所有元素都大于0,若数组大小为0,请返回0。

思路:

  1. 方法1:python中,可以直接min(array) 。我觉得还是避免这种做法,因为主要还是考思维,这么做就没什么意义。
  2. 方法2:先排序,再取第一个。C++: sort(array.begin(),array.end());
    python: sorted(array) 或者 array.sort()
  3. 方法3:二分法,从中间开始比较,略
  4. 方法4:双指针。类似于快速排序的第一轮,慢指针从头开始,快指针从尾开始,向中间靠拢,总会找到最小的那个。代码比二分法的简洁,好理解(我还是比较喜欢这个方法,因为二刷这题的时候,是一瞬间蹦出来的想法,以前第一次做这题,还是比较死脑筋的那种做法)。
int minNumberInRotateArray(vector<int> rotateArray) {
        int len = rotateArray.size();
        int low = 0;
        int high = len - 1;
        while(low < high)
        {
            while(low < high && rotateArray[low] >= rotateArray[high])
                low++;
            while(low < high && rotateArray[high] > rotateArray[low])
                high--;
        }
        return rotateArray[low];
    }

其它

T1. 数据流中的中位数

题目描述
如何得到一个数据流中的中位数?如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值。如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值。我们使用Insert()方法读取数据流,使用GetMedian()方法获取当前读取数据的中位数。

思路:先排序,再分奇偶讨论。其中排序使用的是sort直接排序,也可以边push边排序。
边push边排序的思路是:先push一个num到队列中,比较list[i]和list[i-1],若不满足大小顺序,两数交换,然后将i–,再次比较list[i]和list[i-1],直到到达list中的第一个元素,然后再push一个num到队列,再次进行从后往前的比较。

注意点:

  1. 根据奇偶性来判断输出一个值还是两个值和的平均,可以用一句代码代替(可以避免if else的奇偶判断): return (list[(size - 1) >> 1] + list[size >> 1]) / 2.0;
  2. 如果要输出浮点型,需要/2.0,而不是/2。
  3. 如果要输出浮点型,可以将vector < int > list 修改成vector< float >list;

方法一:使用sort

class Solution {
    vector<int> list;
public:
    void Insert(int num)
    {
        list.push_back(num);    //直接无顺序push
    }
double GetMedian()
    {
        sort(list.begin(), list.end());
        int mid_index = list.size() / 2;
        if (list.size() & 1 == 1)   // 奇数
        {
            return list[mid_index];
        }
        return (list[mid_index] + list[mid_index-1]) / 2.0;    //之所以是2.0而不是2,是因为需要返回double类型
    }
 };

方法二:边push边排序

class Solution2 {
	vector<int> list;
	int size;
public:
	void Insert(int num)
	{
		list.push_back(num);
		size = list.size();
		for (int i = size - 1; i > 0; i--)
		{
			if (list[i] < list[i - 1])
			{   //实现两数交换
				list[i] = list[i] ^ list[i - 1];
				list[i - 1] = list[i] ^ list[i - 1];
				list[i] = list[i] ^ list[i - 1];
			}
		}
	}
	double GetMedian()
	{
		//return (list[(size - 1) >> 1] + list[size >> 1]) / 2.0;
		return (size & 1 == 1) ? list[size / 2] : (list[size / 2] + list[size / 2 - 1]) / 2.0;
	}
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值