Home Authors Posts by 可乐

可乐

419 POSTS 5 COMMENTS

实现一种算法,删除单向链表中间的某个节点(即不是第一个或最后一个节点),假定你只能访问该节点。

示例:

输入:单向链表a->b->c->d->e->f中的节点c
结果:不返回任何数据,但该链表变为a->b->d->e->f

解题思路:只能访问该节点,那么只能对该节点进行操作

void deleteNode(struct ListNode* node) {//给出了目标节点
          struct ListNode* p=NULL;
          struct ListNode* q=NULL;
          p=node->next;
          q=node->next->next;
          node->val=p->val;
          node->next=q;
          free(p);
将目标节点的指向变为其下一个的指向,再释放P;
相爱相杀好基友——数组与链表
作为线性表的两种存储方式 —— 链表和数组,这对相爱相杀的好基友有着各自的优缺点。接下来,我们梳理一下这两种方式。
数组,所有元素都连续的存储于一段内存中,且每个元素占用的内存大小相同。这使得数组具备了通过下标快速访问数据的能力。
但连续存储的缺点也很明显,增加容量,增删元素的成本很高,时间复杂度均为 O(n)。
增加数组容量需要先申请一块新的内存,然后复制原有的元素。如果需要的话,可能还要删除原先的内存。
删除元素时需要移动被删除元素之后的所有元素以保证所有元素是连续的。增加元素时需要移动指定位置及之后的所有元素,然后将新增元素插入到指定位置,如果容量不足的话还需要先进行扩容操作。
总结一下数组的优缺点:
优点:可以根据偏移实现快速的随机读写。
缺点:扩容,增删元素极慢。
链表,由若干个结点组成,每个结点包含数据域和指针域。结点结构如下图所示:
一般来讲,链表中只会有一个结点的指针域为空,该结点为尾结点,其他结点的指针域都会存储一个结点的内存地址。链表中也只会有一个结点的内存地址没有存储在其他结点的指针域,该结点称为头结点。
链表的存储方式使得它可以高效的在指定位置插入与删除,时间复杂度均为 O(1)。
在结点 p 之后增加一个结点 q 总共分三步:
申请一段内存用以存储 q (可以使用内存池避免频繁申请和销毁内存)。
将 p 的指针域数据复制到 q 的指针域。
更新 p 的指针域为 q 的地址。
删除结点 p 之后的结点 q 总共分两步:
将 q 的指针域复制到 p 的指针域。
释放 q 结点的内存。
链表的主要代码
#include <bits/stdc++.h>
using namespace std;
//定义一个结点模板
template<typename T>
struct Node {
T data;
Node *next;
Node() : next(nullptr) {}
Node(const T &d) : data(d), next(nullptr) {}
};
//删除 p 结点后面的元素
template<typename T>
void Remove(Node<T> *p) {
if (p == nullptr || p->next == nullptr) {
return;
}
auto tmp = p->next->next;
delete p->next;
p->next = tmp;
}
//在 p 结点后面插入元素
template<typename T>
void Insert(Node<T> *p, const T &data) {
auto tmp = new Node<T>(data);
tmp->next = p->next;
p->next = tmp;
}
//遍历链表
template<typename T, typename V>
void Walk(Node<T> *p, const V &vistor) {
while(p != nullptr) {
vistor(p);
p = p->next;
}
}
int main() {
auto p = new Node<int>(1);
Insert(p, 2);
int sum = 0;
Walk(p, [&sum](const Node<int> *p) -> void { sum += p->data; });
cout << sum << endl;
Remove(p);
sum = 0;
Walk(p, [&sum](const Node<int> *p) -> void { sum += p->data; });
cout << sum << endl;
return 0;
}
面试问题总结
无法高效获取长度,无法根据偏移快速访问元素,是链表的两个劣势。然而面试的时候经常碰见诸如获取倒数第k个元素,获取中间位置的元素,判断链表是否存在环,判断环的长度等和长度与位置有关的问题。这些问题都可以通过灵活运用双指针来解决。
Tips:双指针并不是固定的公式,而是一种思维方式~
先来看”倒数第k个元素的问题”。设有两个指针 p 和 q,初始时均指向头结点。首先,先让 p 沿着 next 移动 k 次。此时,p 指向第 k+1个结点,q 指向头节点,两个指针的距离为 k 。然后,同时移动 p 和 q,直到 p 指向空,此时 q 即指向倒数第 k 个结点。可以参考下图来理解:
class Solution {
public:
ListNode* getKthFromEnd(ListNode* head, int k) {
ListNode *p = head, *q = head; //初始化
while(k–) {   //将 p指针移动 k 次
p = p->next;
}
while(p != nullptr) {//同时移动,直到 p == nullptr
p = p->next;
q = q->next;
}
return q;
}
};
获取中间元素的问题。设有两个指针 fast 和 slow,初始时指向头节点。每次移动时,fast向后走两次,slow向后走一次,直到 fast 无法向后走两次。这使得在每轮移动之后。fast 和 slow 的距离就会增加一。设链表有 n 个元素,那么最多移动 n/2 轮。当 n 为奇数时,slow 恰好指向中间结点,当 n 为 偶数时,slow 恰好指向中间两个结点的靠后一个(可以考虑下如何使其指向前一个结点呢?)。
下述代码实现了 n 为偶数时慢指针指向靠后结点。
class Solution {
public:
ListNode* middleNode(ListNode* head) {
ListNode *p = head, *q = head;
while(q != nullptr && q->next != nullptr) {
p = p->next;
q = q->next->next;
}
return p;
}
};
是否存在环的问题。如果将尾结点的 next 指针指向其他任意一个结点,那么链表就存在了一个环。
上一部分中,总结快慢指针的特性 —— 每轮移动之后两者的距离会加一。下面会继续用该特性解决环的问题。
当一个链表有环时,快慢指针都会陷入环中进行无限次移动,然后变成了追及问题。想象一下在操场跑步的场景,只要一直跑下去,快的总会追上慢的。当两个指针都进入环后,每轮移动使得慢指针到快指针的距离增加一,同时快指针到慢指针的距离也减少一,只要一直移动下去,快指针总会追上慢指针。
根据上述表述得出,如果一个链表存在环,那么快慢指针必然会相遇。实现代码如下:
class Solution {
public:
bool hasCycle(ListNode *head) {
ListNode *slow = head;
ListNode *fast = head;
while(fast != nullptr) {
fast = fast->next;
if(fast != nullptr) {
fast = fast->next;
}
if(fast == slow) {
return true;
}
slow = slow->next;
}
return nullptr;
}
};
最后一个问题,如果存在环,如何判断环的长度呢?方法是,快慢指针相遇后继续移动,直到第二次相遇。两次相遇间的移动次数即为环的长度。

字符串的左旋转操作是把字符串前面的若干个字符转移到字符串的尾部。请定义一个函数实现字符串左旋转操作的功能。比如,输入字符串”abcdefg”和数字2,该函数将返回左旋转两位得到的结果”cdefgab”。
示例 1:
输入: s = “abcdefg”, k = 2
输出: “cdefgab”
示例 2:
输入: s = “lrloseumgh”, k = 6
输出: “umghlrlose”
解题思路:数组元素的下标变换;利用传递的n作为作为划分点;将其划分为两部分,前后同时进行存入;在同时满足条件时才跳出;达到一次循环结束,最大的循环次数为俩部分中的长度最大值;
//双指针;
char* reverseLeftWords(char* s, int n){
char *arry;
int sum ,m1,m ,i=0 ,j;
    sum=strlen(s);//strlen求其长度;
    j=sum-1;//下标;
arry=malloc(sizeof(int)*(sum+1));
    arry[sum]=’\0′;//字符串数组最后是’\0′
m=n-1;  //向前的指针;
m1=n;  //向后的指针;
while(m1<sum || 0<=m ){
if(0<=m){
arry[j]=s[m];
j–;
m–;
}
if(m1<sum){
arry[i]=s[m1];
i++;
m1++;
}
}
return arry;
}

给你两个整数数组 startTime(开始时间)和 endTime(结束时间),并指定一个整数 queryTime 作为查询时间。

已知,第 i 名学生在 startTime[i] 时开始写作业并于 endTime[i] 时完成作业。

请返回在查询时间 queryTime 时正在做作业的学生人数。形式上,返回能够使 queryTime 处于区间 [startTime[i], endTime[i]](含)的学生人数。

示例 1:

输入:startTime = [1,2,3], endTime = [3,2,7], queryTime = 4
输出:1
解释:一共有 3 名学生。
第一名学生在时间 1 开始写作业,并于时间 3 完成作业,在时间 4 没有处于做作业的状态。
第二名学生在时间 2 开始写作业,并于时间 2 完成作业,在时间 4 没有处于做作业的状态。
第三名学生在时间 3 开始写作业,预计于时间 7 完成作业,这是是唯一一名在时间 4 时正在做作业的学生。

解题思路:相同下标i表示同一学生,判断目标值是否在startTime[i] 与endtime[i]之间,

  • 时间复杂度 O(N): 需要遍历每个元素一次
  • 空间复杂度 O(1): 只使用了几个变量
int busyStudent(int* startTime, int startTimeSize, int* endTime, int endTimeSize, int queryTime){
    int k=0,i;
    for(i=0;i<startTimeSize;i++)
    {
        if(queryTime>=startTime[i]&&queryTime<=endTime[i])
            k++;
    }
    return k;
}

给你一个数组 candies 和一个整数 extraCandies ,其中 candies[i] 代表第 i 个孩子拥有的糖果数目。
对每一个孩子,检查是否存在一种方案,将额外的 extraCandies 个糖果分配给孩子们之后,此孩子有 最多 的糖果。注意,允许有多个孩子同时拥有 最多 的糖果数目。
示例 1:
输入:candies = [2,3,5,1,3], extraCandies = 3
输出:[true,true,true,false,true]
解释:
孩子 1 有 2 个糖果,如果他得到所有额外的糖果(3个),那么他总共有 5 个糖果,他将成为拥有最多糖果的孩子。
孩子 2 有 3 个糖果,如果他得到至少 2 个额外糖果,那么他将成为拥有最多糖果的孩子。
孩子 3 有 5 个糖果,他已经是拥有最多糖果的孩子。
孩子 4 有 1 个糖果,即使他得到所有额外的糖果,他也只有 4 个糖果,无法成为拥有糖果最多的孩子。
孩子 5 有 3 个糖果,如果他得到至少 2 个额外糖果,那么他将成为拥有最多糖果的孩子。

解题思路:首先比较出原有糖果数组的最大值,然后分别拿数组对应元素加额外糖果之后的结果和最大值比较;

方法:先排序;再比较;

bool* kidsWithCandies(int* candies, int candiesSize, int extraCandies, int* returnSize){   
    bool *result = (bool *) malloc(sizeof(bool)*candiesSize);
    int i;
    int max=candies[0];
    for(i=0;i<candiesSize;i++)
    {
        max=max<candies[i]?candies[i]:max;//找到最多的糖果数目;
    }
    for(i=0;i<candiesSize;i++)
    {
        result[i]=candies[i]+extraCandies>=max?true:false;//代码亮点三目运算符;
    }
    *returnSize=candiesSize;   
     return result;
}

给你两个整数,n 和 start 。
数组 nums 定义为:nums[i] = start + 2*i(下标从 0 开始)且 n == nums.length 。
请返回 nums 中所有元素按位异或(XOR)后得到的结果。
示例 1:
输入:n = 5, start = 0
输出:8
解释:数组 nums 为 [0, 2, 4, 6, 8],其中 (0 ^ 2 ^ 4 ^ 6 ^ 8) = 8 。
“^” 为按位异或 XOR 运算符。
解题思路:循环异或输出结果;
int xorOperation(int n, int start){

      int  *nums=(int *)malloc(sizeof(int)*n);

      int  k=0;
      for(int i=1;i<n;i++)
      {  
            k^=start+2*i;
      }
return k;
}

给你一个数组 nums 。数组「动态和」的计算公式为:runningSum[i] = sum(nums[0]…nums[i]) 。
请返回 nums 的动态和。
示例 1:
输入:nums = [1,2,3,4]
输出:[1,3,6,10]
解释:动态和计算过程为 [1, 1+2, 1+2+3, 1+2+3+4] 。
解题思路:原数组上进行操作;
/**
 * Note: The returned array must be malloced, assume caller calls free().
 */
int *runningSum(int *nums, int numsSize, int *returnSize)
{
   for(int i=1;i<numsSize;i++)
        {
               nums[i]+=nums[i-1];
        }
*returnSize=numsSize;
return nums;
}

给你一个整数数组 nums,请你选择数组的两个不同下标 i 和 j,使 (nums[i]-1)*(nums[j]-1) 取得最大值。
请你计算并返回该式的最大值。
示例 1:
输入:nums = [3,4,5,2]
输出:12
解释:如果选择下标 i=1 和 j=2(下标从 0 开始),则可以获得最大值,(nums[1]-1)*(nums[2]-1) = (4-1)*(5-1) = 3*4 = 12 。
解题思路:关键在于找出第一大和第二大的值;
方法一:冒泡排序,求(nums[0])*(nums[1])的值;
方法二:双指针,一个指向最大,一个指向第二大,不用排序;
int maxProduct(int* nums, int numsSize){
int first = nums[0] > nums[1] ? nums[0] : nums[1];
int second = nums[0] > nums[1] ? nums[1] : nums[0];
for (int i = 2; i < numsSize; i++) {
if (nums[i] >= first) {
second = first;
first = nums[i];
} else {
if (nums[i] > second) {
second = nums[i];
}
}
}
return (first – 1) * (second – 1);
}

给你两个长度相同的整数数组 target 和 arr 。
每一步中,你可以选择 arr 的任意 非空子数组 并将它翻转。你可以执行此过程任意次。
如果你能让 arr 变得与 target 相同,返回 True;否则,返回 False 。
示例 1:
输入:target = [1,2,3,4], arr = [2,4,1,3]
输出:true
解释:你可以按照如下步骤使 arr 变成 target:
1- 翻转子数组 [2,4,1] ,arr 变成 [1,4,2,3]
2- 翻转子数组 [4,2] ,arr 变成 [1,2,4,3]
3- 翻转子数组 [4,3] ,arr 变成 [1,2,3,4]
理解题意:目的是比较两数组是否相等
方法一:先排序,再同时移动下标进行比较;
方法二:新建数组K,将target[i]和arr[i]当成其下标,进行K[target[i]]++和K[arr[i]]–操作,若两数组相同,K应该不变;

给你一个数组 nums ,数组中有 2n 个元素,按 [x1,x2,…,xn,y1,y2,…,yn] 的格式排列。

请你将数组按 [x1,y1,x2,y2,…,xn,yn] 格式重新排列,返回重排后的数组。

示例 1:

输入:nums = [2,5,1,3,4,7], n = 3
输出:[2,3,5,4,1,7]
解释:由于 x1=2, x2=5, x3=1, y1=3, y2=4, y3=7 ,所以答案为 [2,3,5,4,1,7]

解题思路:新建数组arr,可知arr[0]=x1,arr[1]=y1,又可知x1与y1之间下标相差n,循环将x1,y1赋值给新数组;

for(int i=0;i<n;i++)

{     arr[i++]=nums[i];

arr[i++]=nums[i+n];

}