数据结构经典习题

Posted Huang_ZhenSheng

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了数据结构经典习题相关的知识,希望对你有一定的参考价值。

目录

顺序表:

习题1:给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素,并返回移除后数组的新长度。

习题2:给你一个有序数组 nums ,请你 原地 删除重复出现的元素,使每个元素 只出现一次 ,返回删除后数组的新长度。

习题3:给你两个有序整数数组 nums1 和 nums2,请你将 nums2 合并到 nums1 中,使 nums1 成为一个有序数组。

链表:

习题1:给你一个链表的头节点 head 和一个整数 val ,请你删除链表中所有满足 Node.val == val 的节点,并返回 新的头节点 。

习题2:给你单链表的头节点 head 请你反转链表并返回反转后的链表。

习题3:给定一个头结点为 head 的非空单链表,返回链表的中间结点。如果有两个中间结点,则返回第二个中间结点。

习题4:输入一个链表,输出该链表中倒数第K个结点。

习题5:将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。

习题6:现有一链表的头指针 ListNode* pHead,给一定值x,编写一段代码将所有小于x的结点排在其余结点之前,且不能改变原来的数据顺序,返回重新排列后的链表的头指针。

习题7:对于一个链表,请设计一个时间复杂度为O(n),额外空间复杂度为O(1)的算法,判断其是否为回文结构。给定一个链表的头指针A,请返回一个bool值,代表其是否为回文结构。保证链表长度小于等于900。

习题8:给你两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点。如果两个链表没有交点,返回 null 。

习题9:给定一个链表,判断链表中是否有环。

   延伸问题:

习题10:给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。

习题11:给你一个长度为 n 的链表,每个节点包含一个额外增加的随机指针 random ,该指针可以指向链表中的任何节点或空节点。


顺序表:

习题1:给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素,并返回移除后数组的新长度。

不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并 原地 修改输入数组。

元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。

int removeElement(int* nums, int numsSize, int val)
{
	int src = 0;
	int dst = 0;
	while (src < numsSize)
	{
		if (nums[src] == val)
		{
			src++;
		}
		else
		{
			nums[dst] = nums[src];
			src++;
			dst++;
		}
	}
	return dst;
}
int main()
{
	int nums[] = { 3, 2, 2, 3, 3, 4 };
	int numsSize = sizeof(nums) / sizeof(nums[0]);
	int val = 3;
	int ret = removeElement(nums,numsSize,val);
	printf("%d\\n",ret);
	return 0;
}

习题2:给你一个有序数组 nums ,请你 原地 删除重复出现的元素,使每个元素 只出现一次 ,返回删除后数组的新长度。

不要使用额外的数组空间,你必须在 原地 修改输入数组 并在使用 O(1) 额外空间的条件下完成。

int removeDuplicates(int* nums, int numsSize )
{
	int src = 0;
	int dest = 0;
	if (numsSize == 0)
		return 0;
	while (src < numsSize)
	{
		if (nums[src] == nums[dest])
		{
			src++;
		}
		else
		{
			dest++;
			nums[dest] = nums[src];
		}
	}
	return dest+1;
}
int main()
{
	int nums[] = { 1, 2, 3, 3, 4, 4, 4, 5 };
	int numsSize = sizeof(nums) / sizeof(nums[0]);
	int ret = removeDuplicates(nums, numsSize);
	printf("%d\\n", ret);
	for (int i = 0; i < ret; i++)
	{
		printf("%d\\n", nums[i]);
	}
	return 0;
}

习题3:给你两个有序整数数组 nums1 和 nums2,请你将 nums2 合并到 nums1 中,使 nums1 成为一个有序数组。

初始化 nums1 和 nums2 的元素数量分别为 m 和 n 。你可以假设 nums1 的空间大小等于 m + n,这样它就有足够的空间保存来自 nums2 的元素。

void merge(int* nums1,int m,int* nums2, int n)
{
	int i1 = m - 1;
	int i2 = n - 1;
	int dest = m + n - 1;
	//取大的给过去
	while (i1 >= 0 && i2 >= 0)
	{
		if (nums1[i1] > nums2[i2])
		{
			nums1[dest] = nums1[i1];
			dest--;
			i1--;
		}
		else
		{
			nums1[dest] = nums2[i2];
			dest--;
			i2--;
		}
	}
	//如果是nums2结束,那就不用处理,因为剩下的数本来就是nums1里面
	//如果是nums1结束,就得把nums2的数据挪过去
	while (i2 >= 0)
	{
		nums1[dest] = nums2[i2];
		dest--;
		i2--;
	}
}
int main()
{
	int nums1[] = { 1, 2, 3, 0, 0, 0 };
	int nums2[] = { 4, 5, 6 };
	int nums1Size = sizeof(nums1) / sizeof(nums2[0]);
	int nums2Size = sizeof(nums1) / sizeof(nums2[0]);
	merge(nums1, 3,nums2,3);
	for (int i = 0; i < nums1Size; i++)
	{
		printf("  %d",nums1[i]);
	}
	return 0;
}

链表:

习题1:给你一个链表的头节点 head 和一个整数 val ,请你删除链表中所有满足 Node.val == val 的节点,并返回 新的头节点 。

 思路2:代码:

//Definition for singly-linked list.
struct ListNode
{
   int val;
   struct ListNode *next;
};
struct ListNode* removeElements(struct ListNode* head, int val)
{
	if (head == NULL)
		return NULL;
	struct ListNode* newnode = NULL;
	struct ListNode* tail = NULL;
	//取不是val的节点尾插
	struct ListNode* cur = head;

	while (cur)
	{
		struct ListNode* next = cur->next;
		if (cur->val == val)
		{
			free(cur);
		}
		else
		{
			//尾插
			if (tail == NULL)
			{
				newnode = tail = cur;
			}
			else
			{
				tail->next = cur;
				tail = cur;
			}
		}
		cur = next;
		if (tail)
			tail->next = NULL;
	}
	return newnode;
}
int main()
{
	struct ListNode*n1 = (struct ListNode*)malloc(sizeof(struct ListNode));
	struct ListNode*n2 = (struct ListNode*)malloc(sizeof(struct ListNode));
	struct ListNode*n3 = (struct ListNode*)malloc(sizeof(struct ListNode));
	struct ListNode*n4 = (struct ListNode*)malloc(sizeof(struct ListNode));
	n1->val = 1;
	n2->val = 2;
	n3->val = 3;
	n4->val = 7;

	n1->next = n2;
	n2->next = n3;
	n3->next = n4;
	n4->next = NULL;
	struct ListNode* newnode = removeElements(n1, 7);
	while (newnode != NULL)
	{
		printf("%d->",newnode->val);
		newnode = newnode->next;
	}
	printf("NULL\\n");
	return 0;
}

习题2:给你单链表的头节点 head 请你反转链表并返回反转后的链表。

方法1:

struct ListNode* reverseList(struct ListNode* head)
{
	if (head == NULL)
		return head;
	struct ListNode*n1 = NULL;
	struct ListNode*n2 = NULL;
	struct ListNode*n3 = NULL;
	n1 = NULL;
	n2 = head;
	n3 = head->next;
	while (n2)
	{
		n2->next = n1;
		n1 = n2;
		n2 = n3;
		if (n3)
			n3 = n2->next;
	}
	return n1;
}

方法2:

struct ListNode* reverseList(struct ListNode* head)
{
    if(head == NULL || head->next ==NULL)
    return head;
    struct ListNode* cur = head;
    struct ListNode* newhead = NULL;
    while(cur)
    {
        struct ListNode* next= cur->next;
        cur->next = newhead;
        newhead = cur;//注意这行代码别顺序别对调了
        cur = next;
    }
    return newhead;
}

习题3:给定一个头结点为 head 的非空单链表,返回链表的中间结点。如果有两个中间结点,则返回第二个中间结点。

struct ListNode* middleNode(struct ListNode* head)
{
    struct ListNode* slow;
    struct ListNode* fast;
    slow = fast = head;
    while(fast && fast->next)
    {
        slow = slow->next;
        fast = fast->next->next;
    }
    return slow;

}

习题4:输入一个链表,输出该链表中倒数第K个结点。

struct ListNode* FindKthToTail(struct ListNode* pListHead, int k )
{
    // write code here
     if(k == 0)
          return NULL;
       struct ListNode*fast = NULL;
       struct ListNode*slow = NULL;
       slow = fast =  pListHead;
           while(k--)
           {
               if(fast == NULL)
                   return NULL;
            fast = fast->next;   
           }
           while(fast)
           {
           slow = slow->next;
           fast = fast->next;
           }
           return slow;
}

习题5:将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。

struct ListNode* mergeTwoLists(struct ListNode* l1, struct ListNode* l2)
{
	if (l1 == NULL)
		return l2;
	if (l2 == NULL)
		return l1;
	struct ListNode*n1 = l1;
	struct ListNode*n2 = l2;
	struct ListNode*newhead = NULL;
	struct ListNode*tail = NULL;
	//给一个哨兵位的头节点,方便尾插,处理完以后,再删掉
	newhead = tail = (struct ListNode*)malloc(sizeof(struct ListNode));

	/*if (n1->val < n2->val)
	{
		newhead = tail = n1;
		n1 = n1->next;
	}
	else
	{
		newhead = tail = n2;
		n2 = n2->next;
	}*/
	while (n1 && n2)
	{
		if (n1->val < n2->val)
		{
			tail->next = n1;
			tail = n1;
			n1 = n1->next;
		}
		else
		{
			tail->next = n2;
			tail = n2;
			n2 = n2->next;
		}
	}
	if (n1)
		tail->next = n1;
	if (n2)
		tail->next = n2;
	struct ListNode* first = newhead->next;
	free(newhead);
	return first;
}

习题6:现有一链表的头指针 ListNode* pHead,给一定值x,编写一段代码将所有小于x的结点排在其余结点之前,且不能改变原来的数据顺序,返回重新排列后的链表的头指针。

思路:将比x大的数拿出来拼在一起,然后再将2个链表连接起来!

class Partition {
public:
	ListNode* partition(ListNode* pHead, int x)
	{
		// write code here
		struct ListNode* lessHead, *lessTail, *greaterHead, *greaterTail;
		lessHead = lessTail = (struct ListNode*)malloc(sizeof(struct ListNode));
		greaterHead = greaterTail = (struct ListNode*)malloc(sizeof(struct ListNode));
		struct ListNode*cur = pHead;
		while (cur)
		{
			if (cur->val < x)
			{
				lessTail->next = cur;
				lessTail = cur;
			}
			else
			{
				greaterTail->next = cur;
				greaterTail = cur;
			}
			cur = cur->next;
		}

		lessTail->next = greaterHead->next;
		struct ListNode*head = lessHead->next;
		free(lessHead);
		free(greaterHead);
		return head;
	}
};

思考:此处代码哪里出问题了?

 

 关注边界,一般链表的边界就是头尾,数组的话就是关注开始或者快结束的位置的处理!

	greaterTail->next = NULL;

习题7:对于一个链表,请设计一个时间复杂度为O(n),额外空间复杂度为O(1)的算法,判断其是否为回文结构。给定一个链表的头指针A,请返回一个bool值,代表其是否为回文结构。保证链表长度小于等于900。

思路:创建一个数组!   问题:空间复杂度不满足O(1) 

方法1: 

bool chkPalindrome(ListNode* A)
    {
        // write code here
        int a[900];
        struct ListNode* cur = A;
        int n = 0;
        while(cur)
        {
            a[n++] = cur->val;
            cur = cur->next;
        }
        int left = 0;
        int right = n-1;
        while(left < right)
        {
        if(a[left] != a[right])
        {
            return false;
        }   
            left++;
            right--;
        }
        return true;
    }

方法2: 

struct ListNode* FindMidNode(struct ListNode* phead)
    {
         struct ListNode*slow;
         struct ListNode*fast;
        slow = fast = phead;
        while(fast && fast->next)
        {
            slow = slow->next;
            fast = fast->next->next;
        }
        return slow;
    }
    struct ListNode* reverseList(struct ListNode* phead)
    {
        struct ListNode*newhead = NULL;
        struct ListNode*cur = phead;
        while(cur)
        {
            struct ListNode*next = cur->next;
            cur->next = newhead;
            newhead = cur;
            cur = next;
        }
        return newhead;
    }
    
    bool chkPalindrome(ListNode* A)
    {
        // write code here
     struct ListNode*mid = FindMidNode(A);
     struct ListNode*rhead = reverseList(mid);
         struct ListNode*head = A;
        while(head && rhead)
        {
            if(head->val != rhead->val)
                return false;
            rhead = rhead->next;
            head = head->next;
        }
        return true;
    }

习题8:给你两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点。如果两个链表没有交点,返回 null 。

struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB)
{
	struct ListNode *curA = headA;
	struct ListNode *curB = headB;
	int lenA = 0;
	int lenB = 0;
	while (curA)
	{
		lenA++;
		curA = curA->next;
	}
	while (curB)
	{
		lenB++;
		curB = curB->next;
	}
	struct ListNode *longlist = headA;
	struct ListNode *shortlist = headB;
	if (lenA < lenB)
	{
		longlist = headB;
		shortlist = headA;
	}
	int gap = abs(lenA - lenB);
	while (gap--)
	{
		longlist = longlist->next;
	}
	while (longlist && shortlist)
	{
		if (longlist == shortlist)
		{
			return longlist;
		}
		longlist = longlist->next;
		shortlist = shortlist->next;
	}
	return NULL;
}

习题9:给定一个链表,判断链表中是否有环。

如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。

如果链表中存在环,则返回 true 。 否则,返回 false 。

思路:快慢指针

bool hasCycle(struct ListNode *head) 
{
    struct ListNode *slow = head;
    struct ListNode *fast = head;
    while(fast && fast->next)
    {
        slow = slow->next;
        fast = fast->next->next;
        if(slow == fast)
        {
            return true;
        }
    }
    return false;
}

延伸问题:

证明:fast一次走2步,slow一次走1步可以,fast一次走3,4 ,5步可以吗?

总结:fast一次走x,slow一次走y步都是可以的,最重要的是,追赶过程中的步差x-y,步差是1,一定能追上,步差不是1就不一定能追上。这里如果fast一次走4,5,6等等同理

习题10:给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回 null

struct ListNode *detectCycle(struct ListNode *head)
{
    struct ListNode *slow= head;
    struct ListNode *fast = head;
    while(fast && fast->next)
    {
        slow = slow->next;
        fast = fast->next->next;
        if(slow == fast)
        {
            struct ListNode *meet = slow;
            while(head != meet)
            {
                head = head->next;
                meet = meet->next;
            }
            return meet;
        }
    }
    return NULL;
}

方法2:调用习题8的解法 :

struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB)
{
	struct ListNode *curA = headA;
	struct ListNode *curB = headB;
	int lenA = 0;
	int lenB = 0;
	while (curA)
	{
		lenA++;
		curA = curA->next;
	}
	while (curB)
	{
		lenB++;
		curB = curB->next;
	}
	struct ListNode *longlist = headA;
	struct ListNode *shortlist = headB;
	if (lenA < lenB)
	{
		longlist = headB;
		shortlist = headA;
	}
	int gap = abs(lenA - lenB);
	while (gap--)
	{
		longlist = longlist->next;
	}
	while (longlist && shortlist)
	{
		if (longlist == shortlist)
		{
			return longlist;
		}
		longlist = longlist->next;
		shortlist = shortlist->next;
	}
	return NULL;
}

struct ListNode *detectCycle(struct ListNode *head)
{
    struct ListNode *slow= head;
    struct ListNode *fast = head;
    while(fast && fast->next)
    {
        slow = slow->next;
        fast = fast->next->next;
        if(slow == fast)
        {
            struct ListNode *meet = slow;
            struct ListNode *next = meet->next;
            meet->next = NULL;
//调用习题8的解法
            return getIntersectionNode(head,next);
        }
    }
    return NULL;
}

习题11:给你一个长度为 n 的链表,每个节点包含一个额外增加的随机指针 random ,该指针可以指向链表中的任何节点或空节点。

struct Node* copyRandomList(struct Node* head)
{
    if(head == NULL)
    return NULL;
    //拷贝节点挂在原节点的后面
    struct Node*cur = head;
    while(cur)
    {
        struct Node*next = cur->next;
        struct Node*copy = (struct Node*)malloc(sizeof( struct Node));
        copy->val = cur->val;
        cur->next = copy;
        copy->next = next;
        cur = next;
    }

    //置random
    cur = head;
    while(cur)
    {
        struct Node*copy = cur->next;
        if(cur->random)
            copy->random = cur->random->next;
        else
            copy->random = NULL; 

        cur = copy->next;

    }
    //3.把原链表的节点解下来,尾插到新链表
    struct Node*copyhead=NULL;
    struct Node*copytail=NULL;
    copyhead = copytail = (struct Node*)malloc(sizeof(struct Node));
    cur  = head;
    while(cur)
    {
        struct Node* copy = cur->next;
        struct Node* next = copy->next;
        copytail->next = copy;
        copytail = copy;

        cur->next = next;
        cur = next;
    }
    struct Node* del = copyhead;
    copyhead = copyhead->next;
    free(del);
    return copyhead;
}

以上是关于数据结构经典习题的主要内容,如果未能解决你的问题,请参考以下文章

500道全网最新python面试习题(大厂面试经典,从此面试不在愁)持续更新中(附源代码)

数据库经典习题,

Python经典编程习题100例,供初学者学习

MySQL基本语句与经典习题

C语言经典习题

数据库经典查询语句与练习题