数据结构经典习题
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 的非空单链表,返回链表的中间结点。如果有两个中间结点,则返回第二个中间结点。
习题5:将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
习题6:现有一链表的头指针 ListNode* pHead,给一定值x,编写一段代码将所有小于x的结点排在其余结点之前,且不能改变原来的数据顺序,返回重新排列后的链表的头指针。
习题8:给你两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点。如果两个链表没有交点,返回 null 。
习题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;
}
以上是关于数据结构经典习题的主要内容,如果未能解决你的问题,请参考以下文章