双指针的四大模型(面试基础)

大家好,我是教授.F

目录

两个指针,方向相反

两个指针,方向相同

两个指针,一快一慢(快慢指针)

两个指针,方向相同,但是起点不同


对于C语言指针可谓是重中之重,写好了不仅对学习其他语言有好处,对面试也是至关重要。

两个指针,方向相反

   目的是在一个顺序数组中,怎么找到数组中俩个元素的和等于目标元素。
   例如数组:int data[] = { 1, 2, 7, 8, 15, 20 },目标值:int target = 9;                                               我们可以用两个指针,一个在前面,一个在最后面,往中间靠拢。

#include<stdio.h>
#include<stdlib.h>
//注意这里应该是int *类型,因为返回的是数组
int * twoNum(int data[], int  size, int target)
{
	int i = 0;
	int j = size - 1;
	int sum=0;
	while (sum < target)
	{
		i++;
		sum = data[i] + data[j];
	}
	while (sum > target)
	{
		j--;
		sum = data[i] + data[j];
	}
	if (i >= j)
	{
		return NULL;
	}
 
	int* ret = malloc(sizeof(int) * 2);
	ret[0] = i+1;
	ret[1] = j+1;
	return ret;
}
int main()
{
	int data[] = { 1, 2, 7, 8, 15, 20 };
	int target = 9;
	int size = sizeof(data) / sizeof(data[0]);
	int* ret = twoNum(data, sizeof(data) / sizeof(data[0]), target);
	if (ret)
	{
		printf("%d %d", ret[0], ret[1]);
	}
	else
	{
		printf("找不到\n");
	}
	
	return 0;
}

两个指针,方向相同


实现有序数组的合并,合并后的新数组,保存有序

我们将两个数组进行合并,怎么保证相加后的数组的元素是按顺序排的呢?

我们可以通过一个指针指向一个数组,另一个指针指向另一个数组。将每一个数组中的元素一一比较,小的就传到新的数组中。



#include<stdio.h>
#include<stdlib.h>
int* merge(int data1[], int n, int data2[], int m)
{
	int i = 0, j = 0;
	int k = 0;
	int* ret = malloc(sizeof(int) * (n + m));
	while (i<n && j<m)
	{
		if (data1[i] < data2[j])
		{
			ret[k++] = data1[i++];
		}
		else
		{
			ret[k++] = data2[j++];
		}
	}
	while (i<n)
	{
			ret[k++] = data1[i++];
	}
	while (j<m)
	{
			ret[k++] = data2[j++];
	}
	return ret;
}
int main()
{
	int data1[] = { 2,5,7,8,10 };
	int data2[] = { 1,3,4,6,9 };
	int n = sizeof(data1) / sizeof(data1[0]);
	int m = sizeof(data2) / sizeof(data2[0]);
	int* ret = merge(data1, n, data2, m);
	for (int i = 0; i < n + m; i++)
	{
		printf("%3d", ret[i]);
	}
	return 0;
}

两个指针,一快一慢(快慢指针)


判断一个单链表,是否存在“环” 我们可以用两个指针,指向同一个地方,但是一个走的比较快,一个走的比较慢。例如下面的代码,一个指针一次走一格,一个指针一次走两个格。然后在几次循环后,两个指针相遇,那能判断这个单链表存在“环”。

在下面代码中,我是判断存在“环”,并且输出环的起始位置。怎么找到环的起始位置是是一个难点。我们不能直接输出相遇的点,因为相遇的点不一定就是起始位置。所以我们还得思考怎么得出起始位置。

我是这么做的:当相遇时,用一个指针指向相遇位置,一个指针指向单链表的表头。这两个指针放在while循环中,直到两个指针相遇,就找到了环的起始位置

为什么这样就找到起始位置了???

大家请看我代码中的解析。

#include <stdio.h>
#include <stdlib.h>

struct ListNode
{
    int val;
    struct ListNode* next;
};

typedef struct ListNode ListNode;

ListNode* detectCycle(ListNode* head)
{
    ListNode* fast = head, * slow = head;

    while (fast != NULL && fast->next != NULL)
    {
        fast = fast->next->next;
        slow = slow->next;
        if (slow == fast)
        {
            ListNode* index1 = head, * index2 = slow;
 
/*在这段代码中,index1和index2分别是快慢指针相遇后的两个指针。
它们相遇的位置正好是环的起始位置。这个结论可以通过数学推导来理解。
假设从链表头到环的起始位置的距离是a,环的起始位置到他们相遇的位置的距离是b,
他们相遇的位置再回到环的起始位置的距离是c。这里b是快指针比慢指针多走的距离,
而c是环的剩余部分。当快慢指针相遇时,慢指针走的距离:a + b,
快指针走的距离:a + b + n * (b + c),这里n是快指针在环内循环的次数,
由于快指针的速度是慢指针的两倍,所以可以得到 2 * (a + b) = a + b + n * (b + c)。
根据以上等式可以解出a = (n - 1) * (b + c) + c,这表示从相遇位置再走a步会回到环的起始位置。
因此,当两个指针相遇后,让其中一个指针回到链表头,然后两个指针以相同的速度移动,
它们相遇的位置就是环的起始位置。当n等于2时,a=c*/
            while (index1 != index2)
            {
                index1 = index1->next;
                index2 = index2->next;
            }
            return index1;
        }
    }
    return NULL;
}

int main()
{
    // 创建一个有循环的链表用于测试
    ListNode* head = (ListNode*)malloc(sizeof(ListNode));
    head->val = 1;
    head->next = (ListNode*)malloc(sizeof(ListNode));
    head->next->val = 2;
    head->next->next = (ListNode*)malloc(sizeof(ListNode));
    head->next->next->val = 3;
    head->next->next->next = head;  // 使链表形成循环,连接到头节点

    ListNode* cycleStart = detectCycle(head);
    if (cycleStart != NULL)
    {
        printf("Cycle starts at node with value: %d\n", cycleStart->val);
    }
    else
    {
        printf("No cycle found in the linked list.\n");
    }

    // 释放链表节点的内存
    free(head->next->next);
    free(head->next);
    free(head);

    return 0;
}

两个指针,方向相同,但是起点不同


返回单链表的倒数第n个节点

例如一个单链表长度未知,我们想返回倒数第三个,那么我们就可以有一个指针指向表头,另一个指针指向第三个。不断往下走,直到一个指针为空,后面的指针就是指向倒数第三个元素了。



#include<stdio.h>
#include<stdlib.h>
typedef int ElemType;
typedef struct LNode
{
	int val;
	struct LNode* next;
}LNode,*LinkList;

int Search_k(LinkList list, int k)
{
	LNode* p = list->next,*q = list->next;
	int count = 0;
	while (p != NULL)
	{
		if (count < k)//让p先走到第k个位置,然后剩下的让q走,
                      //q就能走到(链表长度-k)的位置,也就是倒数第k的位置
		{
			count++;
		}
		else
		{
			 q = q->next;
		}
		p = p->next;
	}
	if (count < k)//节点数小于k
	{
		return 0;
	}
	printf("%d", q->val);
	return 1;
}

int main()
{
	//创建节点
	LinkList list = (LinkList)malloc(sizeof(LNode));
	list->next = NULL;

	LinkList current = list;
	for (int i = 1; i <= 5; i++)
	  {
	    LNode* newNode = (LNode*)malloc(sizeof(LNode));
	    newNode->val= i * 10;
	    newNode->next = NULL;
	    current->next = newNode;
	    current = newNode;
	  }

	int k = 3;
	int result = Search_k(list,k);
	if (result)
	{
		printf("是倒数第%d个元素\n", k);
	}
	else
	{
		printf("这个链表比%d还短",k);
	}
	current = list->next;
	while (current != NULL)
	{
		LinkList tmp = current;
		current = current->next;
		free(tmp);
	}
	free(list);
	return 0;
}

  • 33
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值