核心:删除一个节点的前提是找到它的前一个节点。正常的操作是先找到target的前一个节点,通过前一个节点删除target,思维转换一下可以将target的下一个节点的值赋给target,通过target把它的下一个删除掉,这种方法不适合target为链表最后一个节点的情况,如果target为最后一个节点,则得从头开始找target的前一个节点,即倒数第二个节点。
{% tabs %} {% tab title="O(n)" %}
void delNode(ListNode *head, ListNode *target){
if(head == NULL)
return;
if(head == target){
head = head->next;
delete target;
return;
}
ListNode *ptr = head;
while(ptr){
if(ptr->next == target){
ptr->next = target->next;
delete target;
return;
}
ptr->next;
}
}{% endtab %}
{% tab title="O(1)" %}
void delNode(ListNode *head, ListNode *target){
if(head == NULL)
return;
if(target->next != NULL){
target->val = target->next->val;
ListNode *tmp = target->next;
target->next = target->next->next;
delete tmp;
return;
}else{
ListNode *ptr = head;
while(ptr){
if(ptr->next == target){
ptr->next = target->next;
delete target;
return;
}
ptr->next;
}
}
}{% endtab %} {% endtabs %}
洗牌算法是将原来的数组进行打散,使原数组的某个数在打散后的数组中的每个位置上等概率的出现,刚好可以解决该问题。
由抽牌、换牌和插牌衍生出三种洗牌算法,其中抽牌和换牌分别对应Fisher-Yates Shuffle和Knuth-Durstenfeld Shhuffle算法。
最早提出这个洗牌方法的是 Ronald A. Fisher 和 Frank Yates,即 Fisher–Yates Shuffle,其基本思想就是从原始数组中随机取一个之前没取过的数字到新的数组中,具体如下:
- 初始化原始数组和新数组,原始数组长度为n(已知);
- 从还没处理的数组(假如还剩k个)中,随机产生一个
$$[0, k)$$ 之间的数字p(假设数组从0开始); - 从剩下的k个数中把第p个数取出;
- 重复步骤2和3直到数字全部取完;
- 从步骤3取出的数字序列便是一个打乱了的数列。
下面证明其随机性,即每个元素被放置在新数组中的第
void Fisher_Yates_Shuffle(vector<int>& arr,vector<int>& res)
{
int len = arr.size();
srand((unsigned)time(NULL));
for (int i = 0; i < len; ++i)
{
int k = rand() % arr.size();
res.push_back(arr[k]);
arr.erase(arr.begin() + k);
}
}Knuth 和 Durstenfeld 在Fisher 等人的基础上对算法进行了改进,在原始数组上对数字进行交互,省去了额外
该算法是经典洗牌算法。它的证明如下:
- 对于
arr[i]洗牌后在第n-1个位置的概率是$$1/n$$ (第一次交换的随机数为$$i$$ ) - 在
n-2个位置概率是$$[(n-1)/n] [1/(n-1)] = 1/n$$ ,(第一次交换的随机数不为$$i$$ ,第二次为arr[i]所在的位置(注意,若$$i=n-1$$ ,第一交换arr[n-1]会被换到一个随机的位置)) - 在第
n-k个位置的概率是$$[(n-1)/n]\times [(n-2)/(n-1)] \times \ldots \times[(n-k+1)/(n-k+2)]\times [1/(n-k+1)] = 1/n$$ (第一个随机数不要为$$i$$ ,第二次不为arr[i]所在的位置(随着交换有可能会变)......第$$n-k$$ 次为arr[i]所在的位置)。
void Knuth_Durstenfeld_Shuffle(vector<int> &arr)
{
int len = arr.size();
srand((unsigned)time(NULL));
for (int i = len - 1; i >= 0; --i)
{
int k = rand() % (i + 1);
swap(arr[k], arr[i]);
}
}间复杂度为
Inside-Out Algorithm 算法的基本思思是从前向后扫描数据,把位置i的数据随机插入到前
证明如下:
原数组的第
原数组的第
void Inside_Out_Shuffle(const vector<int>& arr, vector<int>& res)
{
res.assign(arr.size(), 0);
copy(arr.begin(), arr.end(), res.begin());
srand((unsigned)time(NULL));
for (int i = 0; i < arr.size(); ++i)
{
int k = rand() % (i + 1);
swap(res[k], res[i]);
}
}有n台执行器,编号为1...n,一个任务提交后,调度器为其选择一个空闲且编号最小的执行器,将其丢上去运行,如果任务提交时没有空闲的执行器,则等待,直到某一个执行器执行完上一个任务后提交,现在给你一个任务提交的清单,实现调度器,为每个任务分配执行器。
输入:执行器总数n和任务总数m(
输出:每个任务分配的执行器的编号。
用小顶堆:
时间复杂度:
$$mlogn$$