Algorithms and Data Structures (4)

1. Hash table

1. The hash table can be understood as a collection structure at the level of use.
2. If there are only keys and no accompanying data value, you can use the HashSet structure (called UnOrderedSet in C++).
3. If there are both keys and accompanying data value, You can use the HashMap structure (called UnOrderedMap in C++)
4. Whether there is accompanying data or not is the only difference between HashMap and HashSet
. (put) and check (get) operations, the time complexity can be considered as O(1), but the constant time is relatively large 6.
If the things put into the hash table are basic types, they are passed by value internally, and the memory usage is The size of this thing
7. If the thing put into the hash table is not a basic type, it is passed by reference internally, and the memory occupation is the size of the memory address of this thing (both are eight bytes).

2. Ordered table

1. An ordered table can be understood as a collection structure at the level of use.
2. If there are only keys and no accompanying data value, you can use the TreeSet structure (called OrderedSet in C++).
3. If there are both keys and accompanying data value, You can use the TreeMap structure (called OrderedMap in C++)
4. Whether there is accompanying data or not is the only difference between TreeSet and TreeMap. The actual underlying structure is the same thing.
5. The difference between an ordered table and a hash table is that the ordered table The keys are organized in order, while the hash table is not organized at all.
5. Red-black tree, AWL tree, size-balance-tree, and jump table are all ordered table structures, but the underlying specific implementation is different.
6. Put in order table stuff. If it is an underlying type, it is internally passed by value. The memory footprint is the size of this thing.
7. If the thing put into the ordered list is not a basic type, a comparator must be provided, and it is passed by reference internally. The memory occupation is the size of the memory address of this thing.
8. No matter what the underlying implementation is, as long as it is an ordered list, it has the following fixed basic functions and fixed time complexity.

3. Linked list

The node structure of a singly linked list:

class Node {
    
    
public:
    int data;
    Node *next;

    Node(int da = 0, Node *p = NULL) {
    
    
        this->data = da;
        this->next = p;
    }
};

The chain formed by connecting the nodes of the above structure in turn is called a singly linked list structure.
The node structure of the double linked list:

Class Node<V> {
    
    
v value ;
Node next;
Node last;
}

The chain formed by connecting the nodes of the above structure in turn is called a double-linked list structure.
The single-linked list and double-linked list structures only need to give a head node head, and all the remaining nodes can be found.

4. Singly linked list reversal

class Solution {
    
    
    public ListNode reverseList(ListNode head) {
    
    
        if (head == null || head.next == null) {
    
    
            return head;
        }
        ListNode pre = null, cur = head;
        while (cur != null) {
    
    
            ListNode node = cur.next;
            cur.next = pre;
            pre = cur;
            cur = node;
        }
        return pre;
    }
}

Animation display:
Please add a picture description

5. Methodology of Linked List Problem Solving in Interview

1) For the written test, don't care too much about the space complexity, everything is for the time complexity
2) For the interview, the time complexity is still the first, but you must find the most space-saving method.
Important skills:
1) Additional data structure records (Hash table, etc.)
2) Fast and slow pointers
to judge whether a linked list is a palindrome structure
[Title] Given the head node head of a singly linked list, please judge whether the linked list is a palindrome structure.
[Example] 1->2->1, return true; 1->2->2->1, return true; 15->6->15, return true; 1->2->3, return false.

If the space complexity of this problem is not considered, the solution can use the data structure of the stack to solve the above problem. First, all the data in the linked list is put into the stack, and then the outbound operation is performed. If the first number out of the stack is the same as If the first number in the linked list is the same, it will continue to be popped out of the stack. If there is a piece of data that is not matched in the middle, it is not a palindrome.
We can also only push the second half of the linked list into the stack, and then pop it out one by one to compare with the first number of the linked list and the subsequent data, the rules are the same as above.
But the linked list is a data structure with an unknown length. How can we know the intermediate value of the linked list? At this time, we need the operation of the fast and slow pointers, that is, the fast pointer walks two nodes at a time, and the slow pointer walks one node at a time. When the pointer reaches the end of the linked list, the slow pointer also points to the end of the linked list.
code show as below:

#include<iostream>
#include<vector>
using namespace std;
//定义一个节点的结构体,该结构体包括指针域和数值域
typedef struct ListNode {
    
    
	int val;
	struct ListNode* next;
}ListNode, *List;

//创建新节点
void createList(List L, int n) {
    
    
	//记录头结点
	ListNode* r = L;
	for (int i = 0; i < n; i++) {
    
    
		//创建一个新的节点
		ListNode* p = new ListNode;
		//给新的节点赋值
		cin >> p->val;
		//将新节点的结尾值赋空
		p->next = NULL;
		//将头结点的指针域指向新节点的地址
		r->next = p;
		//节点向下移动
		r = p;
	}
}

bool isPalindrome(List L) {
    
    
	vector<int> nums;
	while (L) {
    
    
		nums.push_back(L->val);
		L = L->next;
	}
	vector<int>temp = nums;
	reverse(temp.begin(), temp.end());
	for (int i = 0; i < nums.size(); i++) {
    
    
		if (nums[i] != temp[i])
			return false;
	}
	return true;
}

[Example] If the length of the linked list is N, the time complexity reaches O(N), and the extra space complexity reaches O(1).
But if the additional space complexity is limited to O(1), we can use the operation of the fast and slow pointers. When the slow pointer finds the end point, we need to reverse the linked list after the slow pointer. After the reversal is completed, set two One pointer, one pointer from the beginning of the linked list and one pointer from the end of the linked list, compare them one by one. The comparison mechanism is the same as above. After returning true or false, we restore the second half of the linked list to the previous state to complete the operation.


// 链表长这样

struct ListNode {
    
    
	int val;
	ListNode *next;
	ListNode() : val(0), next(nullptr) {
    
    }
	ListNode(int x) : val(x), next(nullptr) {
    
    }
	ListNode(int x, ListNode *next) : val(x), next(next) {
    
    }
};

class Solution {
    
    
public:
    bool isPalindrome(ListNode* head) {
    
    
		//如果传进来的是空指针或者只有一个值 直接返回true
        if(head->next == nullptr||head == nullptr)
            return true;
        //定义快慢指针
        struct ListNode *slow = head;
        struct ListNode *fast = head;
        
        //通过快慢指针找出右半边链表的头
        while(fast->next != nullptr && fast->next->next != nullptr){
    
    
                slow = slow->next;
                fast = fast->next->next;
        }
        fast = slow->next;	 //此时fast为右半链表的头地址
        slow->next = NULL;  //在左边结束的节点之后设置一个空值
        
        //下面是实现右半链表的反转
        struct ListNode *store = NULL;
        while(fast != nullptr){
    
    
        	//记录左边第一个节点的地址
            store = fast->next;
            //将右边头结点的指针域指向左边的尾部
            fast->next = slow;
            //两个辅助指针右移
            slow = fast;
            fast= store;
        }
	
		//右边最后一个节点的指针
        store = slow;
        //记录左边第一个指针
        fast = head.next;
        bool res = true;
        //最终就是从头到尾开始比较各结点的值 如果出现不相等的则return false
        while(slow != nullptr && fast != nullptr){
    
    
            if(slow->val != fast->val)
            {
    
    
	            res = false;
	            break;
            }                   
            //两个指针同时移动
            rhead = slow->next;
            fast = fast->next;
        }
        
        //将右半部分的链表再逆序回去
        //记录右边第一个节点的地址
        slow = store.next;
        store.next = NULL;
		while(slow != NULL)
		{
    
    
			fast = slow.next;
			slow.next = store;
			store = slow;
			slow = fast;
		}
		return res;
    }
};

6. Divide the one-way linked list into a form with small left, equal middle and large right according to a certain value

[Title] Given a head node head of a singly linked list, the value type of the node is an integer, and an integer pivot is given. Implement a function to adjust the linked list, and adjust the linked list so that the left part is all nodes whose value is less than pivot, the middle part is all nodes whose value is equal to pivot, and the right part is all nodes whose value is greater than pivot.
[Advanced] Add the following requirements on the basis of realizing the function of the original problem
[Requirement] After adjustment, the relative order of all nodes smaller than pivot is the same as before adjustment
[Requirement] After adjustment, the relative order of all nodes equal to pivot Same as before adjustment
[Requirement] After adjustment, the relative order of all nodes larger than pivot is the same as before adjustment
[Requirement] Please achieve O(N) time complexity and O(1) extra space complexity.

 //将链表分成三个部分:
//分别为小于p、等于p、大于p
//分别用一串链表连接起来,最终,连接成一个链表
ListNode* listPartition2(ListNode* head, int num) {
    
    
	ListNode* sH = NULL;
	ListNode* sT = NULL;
	ListNode* eH = NULL;
	ListNode* eT = NULL;
	ListNode* bH = NULL;
	ListNode* bT = NULL;
	ListNode* next = NULL;
	while (head != NULL) {
    
    
		next = head->next;
		head->next = NULL;
		if (head->val < num) {
    
    
			if (sH == NULL) {
    
    
				sT = head;
				sH = head;
			}
			else {
    
    
				sT->next = head;
				sT = head;
			}
		}
		else if (head->val > num) {
    
    
			if (bH == NULL) {
    
    
				bH = head;
				bT = head;
			}
			else {
    
    
				bT->next = head;
				bT = head;
			}
		}
		else{
    
    
			if (eH == NULL) {
    
    
				eH = head;
				eT = head;
			}
			else {
    
    
				eT->next = head;
				eT = head;
			}
		}
		head = next;
	}
	if (sT != NULL) {
    
    
		sT->next = eH;
		eT = eT == NULL ? sT : eT;
	}
	if (eT != NULL) {
    
    
		eT->next = bH;
	}
	return sH != NULL ? sH : (eH != NULL ? eH : bH);
}

7. Copy a linked list containing random pointer nodes

[Title] A special singly linked list node class is described as follows

class Node 
{
    
    
	int value;
	Node next;
	Node rand;
	Node(int val){
    
    
		value = val;
	}
}

The rand pointer is a newly added pointer in the node structure of the singly linked list, and rand may point to any node in the linked list, or may point to null. Given a head node head of an acyclic singly linked list composed of Node node types, please implement a function to complete the copy of this linked list and return the head node of the copied new linked list.
[Requirements] Time complexity 0(N), additional space complexity 0(1)

ListNode* copyListWithRand(ListNode* head) {
    
    
	if (head == NULL) {
    
    
		return NULL;
	}
	ListNode* cur = head;
	1->1'->2
	while (cur != NULL) {
    
    
		//克隆出一个新的节点
		ListNode* node = new ListNode(cur->val);
		//将当前节点的指针指向赋值给克隆出新节点的指向
		node->next = cur->next;
		//将当前节点指向新的节点
		cur->next = node;
		//向下移动
		cur = node->next;
	}
	cur = head;
	while (cur != NULL) {
    
    
		//找到克隆节点
		ListNode* node = cur->next;
		//将原节点的rand赋给克隆节点的rand
		node->random = cur->random == NULL ? NULL : cur->random->next;
		//指针向下移动
		cur = node->next;
	}
	
	//开始分离原结点和克隆节点
	cur = head;
	//设置克隆节点的头
	ListNode* cloneHead = head->next;
	while (cur->next != NULL){
    
       //注意边界处理,不然容易溢出
		//克隆结点
		ListNode* node = cur->next;
		//将克隆结点的指针指向赋值给原结点的指针域
		cur->next = node->next;
		//指针移动
		cur = node;
	}
	return cloneHead;
}

8. A series of questions about the intersection of two singly linked lists

A singly linked list may or may not have a cycle. Given the head nodes head1 and head2 of two singly linked lists, the two linked lists may or may not intersect. Please implement a function. If the two linked lists intersect, please return the first node of the intersect; if not, return null.
[Requirements] If the sum of the lengths of the two linked lists is N, the time complexity should be 0(N), and the additional space complexity should be O(1).
Ideas:
1. First judge whether the two linked lists have a ring, using the method of fast and slow pointers. The slow pointer starts from the second element of the linked list, and the fast pointer starts from the third element of the linked list. If the linked list has a ring, the two pointers must meet in the ring. If the fast pointer reaches NULL first, there is no ring. Then the fast pointer returns to the initial node of the linked list, the slow pointer does not move, and the two pointers start to move an element at the same time. Then the two pointers will meet at the loop-entry node.
2. After judging whether there is a ring or not, it needs to be discussed according to the situation.
**First of all, both linked lists are acyclic: **Record the length of the two linked lists, and judge whether the last nodes of the two linked lists are consistent. If they are consistent, they must intersect. Then the initial value of the pointer of the long linked list is set at the length interpolation of the two linked lists, the pointer of the short linked list starts from the beginning, and the first node where the two pointers meet is the first node where the two pointers meet.
**Both have loops: **First judge whether the loop-entry nodes of the two linked lists are consistent. If they are consistent, return to the previous question of the intersection of the two linked lists without loops. The only difference is that the two cases are counting When the termination node is inconsistent, the above is that both pointers point to NULL to end, and in this case, it ends at the first ring-entry node. However, the judgments of conditions 1 and 3 in the figure below are consistent. If the two loop-entry nodes are inconsistent, use one of the loop-entry nodes to continue down. If they are equal, it is a three-case, otherwise it is a one-case. If it is case 1, there is no intersecting node. If it is case 3, the first intersection point of two ring-entry nodes can be any one of the two.
**A case where there is a ring and one without a ring:** This case does not intersect.
insert image description here

/*
这个问题要考虑几种情况:
1. 两个链表都是单链表,判断入环节点
2. 两个链表都有环,判断入环节点
3. 一个链表有环,一个链表无环,则一定不想交。因为它们都是单链表。
*/
ListNode* findFirst(ListNode* head1, ListNode* head2){
    
    
	//判断两个结点是否有环
	ListNode* loop1 = isLoop(head1);
	ListNode* loop2 = isLoop(head2);
	//如果都无环
	ListNode* firstNode = NULL;
	if (loop1 == NULL && loop2 == NULL) {
    
    
		firstNode = noLoop(head1, head2);
	}	
	//如果都有环
	else if (loop1 != NULL && loop2 != NULL) {
    
    
		firstNode = bothLoop(head1, head2, loop1, loop2);
	}
	//如果一个有环一个无环
	else {
    
     
		firstNode = NULL;
	}
	return firstNode;
}
 
//判断一个链表是否有环
//返回入环的节点
 ListNode* isLoop(ListNode* head) {
    
    
	if (head == NULL || head->next == NULL || head->next->next == NULL) {
    
    
		return NULL;
	}
	
	//设置快慢指针的初始位置
	ListNode* slow = head->next;
	ListNode* fast = head->next->next;
	
	//如果快指针或者慢指针到了NULL,则返回
	while (slow != fast) {
    
    
		if (fast->next == NULL || fast->next->next == NULL) {
    
    
			return NULL;
		}
		slow = slow->next;
		fast = fast->next->next;
	}
	//将快指针设置为头结点
	fast = head;
	//两个指针开始同时只移动一步
	while (slow != fast) {
    
    
		slow = slow->next;
		fast = fast->next;
	}
	//返回遇到的第一个结点
	return slow;
}
 
//都无环的情况
ListNode* noLoop(ListNode* head1, ListNode* head2) {
    
    
	if (head1 == NULL || head2 == NULL) {
    
    
		return NULL;
	}
	//设置暂存链表长度的变量n
	int n = 0;
	ListNode* cur1 = head1;
	ListNode* cur2 = head2;
	//记录第一个链表的长度
	while (cur1->next != NULL) {
    
    
		n++;
		cur1 = cur1->next;
	}
	//记录第二个链表与第一个链表的插值
	while (cur2->next != NULL){
    
    
		n--;
		cur2 = cur2->next;
	}
	//如果两个链表最后一个结点不相等,则不相交
	if (cur1 != cur2) {
    
    
		return NULL;
	}
	//如果第一个链表长,则将第一个头结点赋值给变量cur1,否则将第二个头结点赋值给变量cur1
	cur1 = n > 0 ? head1 : head2;
	//将其余一个链表的头节点赋值给变量cur2
	cur2 = cur1 == head1 ? head2 : head1;
	//对n进行取绝对值操作
	n = abs(n);
	//将较长链表的指针设定在两个链表长度差大小的结点处
	while (n != 0) {
    
    
		n--;
		cur1 = cur1->next;
	}
	//两个指针同时移动
	while (cur1 != cur2) {
    
    
		cur1 = cur1->next;
		cur2 = cur2->next;
	}
	//返回第一个相交的结点
	return cur1;
}
 
//都有环的情况
ListNode* bothLoop(ListNode* head1, ListNode* head2, ListNode* loop1, ListNode* loop2) {
    
    
	ListNode* cur1 = NULL;
	ListNode* cur2 = NULL;
	//首先如果符合上图中的情况二
	if (loop1 == loop2) {
    
    
		int n = 0;
		cur1 = head1;
		cur2 = head2;
		//记录链表一的长度
		while (cur1 != loop1) {
    
    
			n++;
			cur1 = cur1->next;
		}
		//记录链表一与链表二的长度
		while (cur2 != loop2) {
    
    
			n--;
			cur2 = cur2->next;
		}
		//与上一个情况一致
		cur1 = n > 0 ? head1 : head2;
		cur2 = cur1 == head1 ? head2 : head1;
		n = abs(n);
		while (n != 0){
    
    
			n--;
			cur1 = cur1->next;
		}
		while (cur1 != cur2){
    
    
			cur1 = cur1->next;
			cur2 = cur2->next;
		}
		return cur1;
	}
	//判断情况一三
	else {
    
    
		cur1 = loop1->next;
		//判断是否符合情况三
		while (cur1 != loop1){
    
    
			if (cur1 == loop2) {
    
    
				return loop1;
			}
			cur1 = cur1->next;
		}
		return NULL;
	}
}

Supongo que te gusta

Origin blog.csdn.net/qq_52302919/article/details/131056637
Recomendado
Clasificación