初级算法集合

209 阅读7分钟

1.字符串反转

void char_reverse (char *cha) {

    // 定义头部指针
    char *begin = cha;
    // 定义尾部指针
    char *end = cha + strlen(cha) -1;
    
    
    while (begin < end) {
        
        char temp = *begin;
        *(begin++) = *end;
        *(end--) = temp;
    }
}
使用示例
char ch[] = "Hello World";

char_reverse (ch);

// 有兴趣的可以验证
printf ("%s",ch);

2.链表反转(头差法)

1..h声明文件
#import <Foundation/Foundation.h>

// 定义一个链表
struct Node {
    int data;
    struct Node *next;
};

@interface ReverseList : NSObject

// 链表反转
struct Node* reverseList(struct Node *head);

// 构造一个链表
struct Node* constructList(void);

// 打印链表中的数据
void printList(struct Node *head);

@end
1..m实现文件
#import "ReverseList.h"

@implementation ReverseList

struct Node* reverseList(struct Node *head)
{
    // 定义遍历指针,初始化为头结点
    struct Node *p = head;
    
    // 反转后的链表头部
    struct Node *newH = NULL;
    
    // 遍历链表
    while (p != NULL) {
        
        // 记录下一个结点
        struct Node *temp = p->next;
        // 当前结点的next指向新链表头部
        p->next = newH;
        // 更改新链表头部为当前结点
        newH = p;
        // 移动p指针
        p = temp;
    }
    
    // 返回反转后的链表头结点
    return newH;
}

struct Node* constructList(void)
{
    // 头结点定义
    struct Node *head = NULL;
    // 记录当前尾结点
    struct Node *cur = NULL;
    
    for (int i = 1; i < 5; i++) {
        struct Node *node = malloc(sizeof(struct Node));
        node->data = i;
        
        // 头结点为空,新结点即为头结点
        if (head == NULL) {
            head = node;
        }
        // 当前结点的next为新结点
        else{
            cur->next = node;
        }
        
        // 设置当前结点为新结点
        cur = node;
    }
    
    return head;
}

void printList(struct Node *head)
{
    struct Node* temp = head;
    while (temp != NULL) {
        printf("node is %d \n", temp->data);
        temp = temp->next;
    }
}

@end

3.有序数组合并

1..h声明文件
#import <Foundation/Foundation.h>

@interface MergeSortedList : NSObject
// 将有序数组a和b的值合并到一个数组result当中,且仍然保持有序
void mergeList(int a[], int aLen, int b[], int bLen, int result[]);

@end
1..m实现文件
#import "MergeSortedList.h"

@implementation MergeSortedList

void mergeList(int a[], int aLen, int b[], int bLen, int result[])
{
    int p = 0; // 遍历数组a的指针
    int q = 0; // 遍历数组b的指针
    int i = 0; // 记录当前存储位置
    
    // 任一数组没有到达边界则进行遍历
    while (p < aLen && q < bLen) {
        // 如果a数组对应位置的值小于b数组对应位置的值
        if (a[p] <= b[q]) {
            // 存储a数组的值
            result[i] = a[p];
            // 移动a数组的遍历指针
            p++;
        }
        else{
            // 存储b数组的值
            result[i] = b[q];
            // 移动b数组的遍历指针
            q++;
        }
        // 指向合并结果的下一个存储位置
        i++;
    }
    
    // 如果a数组有剩余
    while (p < aLen) {
        // 将a数组剩余部分拼接到合并结果的后面
        result[i] = a[p++];
        i++;
    }
    
    // 如果b数组有剩余
    while (q < bLen) {
        // 将b数组剩余部分拼接到合并结果的后面
        result[i] = b[q++];
        i++;
    }
}

@end

4.查找第一个只出现一次的字符(Hash查找)

1..h声明文件
#import <Foundation/Foundation.h>

@interface HashFind : NSObject

// 查找第一个只出现一次的字符
char findFirstChar(char* cha);

@end
1..m实现文件
#import "HashFind.h"

@implementation HashFind

char findFirstChar(char* cha)
{
    char result = '\0';
    
    // 定义一个数组 用来存储各个字母出现次数
    int array[256];
    
    // 对数组进行初始化操作
    for (int i=0; i<256; i++) {
        array[i] =0;
    }
    // 定义一个指针 指向当前字符串头部
    char* p = cha;
    // 遍历每个字符
    while (*p != '\0') {
        // 在字母对应存储位置 进行出现次数+1操作
        array[*(p++)]++;
    }
    
    // 将P指针重新指向字符串头部
    p = cha;
    // 遍历每个字母的出现次数
    while (*p != '\0') {
        // 遇到第一个出现次数为1的字符,打印结果
        if (array[*p] == 1)
        {
            result = *p;
            break;
        }
        // 反之继续向后遍历
        p++;
    }
    
    return result;
}

@end

5.查找两个子视图的共同父视图

1..h声明文件
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
@interface CommonSuperFind : NSObject

// 查找两个视图的共同父视图
- (NSArray<UIView *> *)findCommonSuperView:(UIView *)view other:(UIView *)viewOther;

@end
1..m实现文件
#import "CommonSuperFind.h"

@implementation CommonSuperFind

- (NSArray <UIView *> *)findCommonSuperView:(UIView *)viewOne other:(UIView *)viewOther
{
    NSMutableArray *result = [NSMutableArray array];
    
    // 查找第一个视图的所有父视图
    NSArray *arrayOne = [self findSuperViews:viewOne];
    // 查找第二个视图的所有父视图
    NSArray *arrayOther = [self findSuperViews:viewOther];
    
    int i = 0;
    // 越界限制条件
    while (i < MIN((int)arrayOne.count, (int)arrayOther.count)) {
        // 倒序方式获取各个视图的父视图
        UIView *superOne = [arrayOne objectAtIndex:arrayOne.count - i - 1];
        UIView *superOther = [arrayOther objectAtIndex:arrayOther.count - i - 1];
        
        // 比较如果相等 则为共同父视图
        if (superOne == superOther) {
            [result addObject:superOne];
            i++;
        }
        // 如果不相等,则结束遍历
        else{
            break;
        }
    }
    
    return result;
}

- (NSArray <UIView *> *)findSuperViews:(UIView *)view
{
    // 初始化为第一父视图
    UIView *temp = view.superview;
    // 保存结果的数组
    NSMutableArray *result = [NSMutableArray array];
    while (temp) {
        [result addObject:temp];
        // 顺着superview指针一直向上查找
        temp = temp.superview;
    }
    return result;
}

@end

6.无序数组中的中位数(快排思想)

还有一种比较好理解,排序后直接求值就可以

1..h声明文件
#import <Foundation/Foundation.h>

@interface MedianFind : NSObject

// 无序数组中位数查找
int findMedian(int a[], int aLen);

@end
1..m实现文件
#import "MedianFind.h"

@implementation MedianFind

//求一个无序数组的中位数
int findMedian(int a[], int aLen)
{
    int low = 0;
    int high = aLen - 1;
    
    int mid = (aLen - 1) / 2;
    int div = PartSort(a, low, high);
    
    while (div != mid)
    {
        if (mid < div)
        {
            //左半区间找
            div = PartSort(a, low, div - 1);
        }
        else
        {
            //右半区间找
            div = PartSort(a, div + 1, high);
        }
    }
    //找到了
    return a[mid];
}

int PartSort(int a[], int start, int end)
{
    int low = start;
    int high = end;
    
    //选取关键字
    int key = a[end];
    
    while (low < high)
    {
        //左边找比key大的值
        while (low < high && a[low] <= key)
        {
            ++low;
        }
        
        //右边找比key小的值
        while (low < high && a[high] >= key)
        {
            --high;
        }
        
        if (low < high)
        {
            //找到之后交换左右的值
            int temp = a[low];
            a[low] = a[high];
            a[high] = temp;
        }
    }
    
    int temp = a[high];
    a[high] = a[end];
    a[end] = temp;
    
    return low;
}

@end

7.两数之和为特定值(简单)

给定一个整数数组和一个目标值,找出数组中和为目标值的两个数。

你可以假设每个输入只对应一种答案,且同样的元素不能被重复利用。

示例

给定 nums = [2, 7, 11, 15], target = 9

因为 nums[0] + nums[1] = 2 + 7 = 9

所以返回 [0, 1]

8.求出数组中连续数字的和值

举例

// 伪代码
[2,3,3,3,3,9,9,1,2,6,6,6,6,6,7]

上面这个数组相同连续数字的和为 6+6+6+6+6 = 30

9.白鼠与毒酒的算法问题。

原文链接

设有 N 桶酒,有一桶是毒酒,编号从0N-1,最少要 K 只白鼠,显然:

当N=2,K=1 
当N=3,K=2 
当N=4,K=2 
4桶酒编号0123 
K=2,设有白鼠A和白鼠B 
A喝0,1,B喝0,2 
一星期后,有4种可能状态 
A死B死(0号有毒),A死B活(1号有毒),A活B死(2号有毒),A活B活(3号有毒) 

猜想白鼠的最终状态只有死活两种可能,通过白鼠一星期后的状态可以算出毒酒编号

很自然想到二进制,例如:

当N=4时,0=00,1=01,2=10,3=11 
当N=8,K=3 
0=000,1=001,2=010,3=0114=1005=1016=1107=111 
白鼠A,B,C 
A喝0123 (0XX) 
B喝0145 (X0X) 
C喝0246 (XX0) 
ABC的最终状态可以确定毒酒编号 
当N=1000时,可以类推至少要10只,你可以这样推出这10只白鼠具体是喝哪些编号的酒,规律很明显了 

回过来再想想,1000之内的任意一个数都可以用一个10位的二进制数表示(不够的话前面补0),白鼠与数位对应,第几位为0,则说明第几位的白鼠死了,而这个二进制的编号即为毒酒编号

10.在一个数组中找出前四个最大的数字。

13.如何验证一个 IP 地址的有效性?

题目复现

题目:如何判断一个IP是否是合法的IP,如输入:192.168.1.0,输出:合法;输入192.168.1.1222,输出:非法。

考察目标

首先这个算法题目在面试中我用亲身遇到过,从面试经历中来看面试官出这道题的目的主要是为了考察对边界限制条件的考察和相关边界条件的考察,归纳一下的话就是简单的一个字符串的处理。

题目分析

我们应该知道IP的格式(具体呈现的规律要有一个总结,不能仅自己知道,需要具体),形式具体如下:(0255).(0255).(0255).(0255)。一共有4个数块(或者字符块)括号中给出了相关限制,然后一共有3个点(英文半角)那么由此可见可以有两种方法实现,一种是基于对字符串的处理,另一种是通过强大的正则表达式来判断,通过上面的考察目标可知面试官更倾向于你编写之前的这种,后者如果你写了只能体现你对你所使用的这种语言正则库的熟悉并不会很浓墨重彩的体现你的筛选思想

限制条件分析

  • 输入的字符串为空则返回 false
  • 算上 “.” 分隔符超过了总的最大长度或者小雨最短长度返回 false
  • 分割后每个小块总数不等于 4 返回 false
  • 分割后每个小块中必须也只能是 0~9 的数字,别的字符违法
  • 分割后的每个小块数值必须在 0~255 之间

实现代码


#include <boost/algorithm/string.hpp>
#include <iostream>
#include <string>
#include <vector>

using namespace std;
using namespace boost;

//字符串分割函数
// vector<string> split(string str,string pattern)
// {
//     string::size_type pos;
//     vector<string> result;
//     str+=pattern;
//     int size=str.size();
//     for(int i=0; i<size; i++)
//     {
//         pos=str.find(pattern,i);
//         if(pos<size)
//         {
//             std::string s=str.substr(i,pos-i);
//             result.push_back(s);
//             i=pos+pattern.size()-1;
//         }
//     }
//     return result;
// }
//当然这个地方自己写的分割函数肯定是没有哪些很成熟的第三方的库函数写的好的,所以这个地方的话最好还是用boost库,其中就包含有split函数,当然了如果你是用的Java写的这个面试题目那就可以直接调用库函数。

bool isIPAddressValid(const string &IPAddr)
{
    //限制条件1
    if(IPAddr == NULL){
        return false;
    }
    //限制条件2
    if(IPAddr.length()<7 || IPAddr.length>15){
        return false;
    }
    //限制条件3,过滤分割输入后的不正常长度
    vector<string> store;
    spilt(store,IPAddr,is_any_of(".");
    if(store.size() != 4){
        return false;
    }
    // vector
    // char* p0 = store[0].c_str();
    // char* p1 = store[1].c_str();
    // char* p2 = store[2].c_str();
    // char* p3 = store[3].c_str();

    //限制条件4,过滤输入可能是非数字字符
    for(int i = 0; i < store.length(); i++){
        //对分割得到的每个字符串的每个字符进行逐一判断,如果不是数字0-9,则判定为非法IP
        for(int j = 0; j < store[i].length(); j++){
            if (store[i].c_str()[j] < '0' || arr[i].c_str()[j] > '9'){
                return false;
            }
        }
    }
    
    //限制条件5,过滤输入的数字是否在0~255
    for(int i = 0; i < store.length(); i++){
        if(store[i] < "0" || store[i] > "255"){
            return false;
        }else{
            i++;
        }
    }

    //如果经过前面验证都没返回到false则返回true
    return true;
}

int main(void){
    string IP_arr;
    cin>> IP_arr;
    if(isIPAddressValid(IP_arr)){
        cout<<"IP address is valid"<<endl
    }else{
        cout<<"IP address is invalid"<<endl;
    }
    return 0;
}

总结

尽管这道题目考察的是对 IP 字符串的筛选方法,但是值得注意的是这个地方的对字符串的分割十分的重要,因为一开始 C++ 并不像 Java 有这个成员函数,所以在转换的时候就是想的是分割出来的一串相邻之间用空格分开即可,但是这个就很不方便我们后面的处理了,看了第三方库函数对分割函数的处理才发现,嗯确实分割之后应该是放在已经容器当中的,这样存取方便(顿时感觉学 vector(或者说容器) 血赚)然后还有一个就是在第 4 个限制条件当编写当中时需要针对每个小块当中的数字进行分析即需要将 string 转换成为 char* 方便比较,C++ 的这个 string 这个地方就很蛋疼,别的基本上都很好理解很限制。对于正则表达式的这种写法,建议大家可以用别的语言试试,比如 Python 什么的,还是很便捷的。

快速排序(Quick Sort)

思想

快速排序可以这样的大致区分为三个步骤

  • 1、选择基准,先从数组(或者容器)中(随机)取出一个数作为基准数

  • 2、分区过程,遍历数组将其中比这个数大的数全放到它的右边,小于或等于它的数全放到它的左边(默认从左至右为从小到大的排列)

  • 3、重复区分过程,再对左右区间重复第二步,直到各区间只有一个数,也就是最后左右两个指针指向了同一个位置。

Partition

快速排序中的 Partiton 函数的主要目的如下,给定一个数组 array[] 和 数组 中任意一个元素 a(一般快排中是选择下标最大的那个元素),重排数组使得 a 左边都小于它,右边都不小于它。

当然现在这个地方讲 Partition 的话单纯是针对 快速排序,但是 Partition 的使用不仅仅仅限于此,举个例子如果我想查找一堆数中的第 K 大的数用 Partition 也是可以实现的,大致的思路如下:

  • 随机找个一个数进行 Partion
  • 将 Partion 操作之后的这个随机数的索引 +1 与 K 比较
  • 如果比 K 小 则在右部分继续,否则在左部分继续,直到索引 +1 == K (以上默认排列顺序从左到右为从小到大

实现

C++ 实现代码如下:

Partiton:

int Partition(vector<int> arr, int left, int right, int RandIndex){
    if(RandIndex <= right && RandIndex >= left){
    int Flag = arr[RandIndex];
    swap(arr[RandIndex],arr[right]);
    int storeIndex = left;
    for(int i = left; i < right; i++){
        if(arr[i] < Flag){
            swap(arr[i],arr[storeIndex]);
            ++storeIndex;
        }
    }
    swap(arr[RandIndex],arr[right]);
    return storeIndex;
    }
}
    2 9 8 4 1 7 | 4 storeIndex = 0  i = 0
    2 9 8 7 1 4 | 4 storeIndex = 1  i = 1
    2 9 8 7 1 4 | 4 storeIndex = 1  i = 2
    2 9 8 7 1 4 | 4 storeIndex = 1  i = 3
    2 9 8 7 1 4 | 4 storeIndex = 1  i = 4
    2 1 8 7 9 4 | 4 storeIndex = 2  i = 5
    jump out for loop
    2 1 4 7 9 8 | 4 storeIndex = 2
    return storeIndex = 2

Quick Sort

//Recursion version
void QuickSort(int r[],int first,int end,Random(first,end))
{
	int index=0;
	if(first<end)
	{
		index=Partition(r,first,end);
		QuickSort(r,first,index-1);//前半部分递归
		QuickSort(r,index+1,end);//后半部分递归
	}
}

冒泡排序

    int a[10] = {3,2,1,4,5,0,6,7,8,9};
    
    int temp = 0;

    for (int i = 0; i < 10 -1; i++) {
        
        for (int j = 0; j < 10 - i - 1; j++) {
            
            if (a[j] > a[j+1]) {
                
                temp = a[j+1];
                a[j+1] = a[j];
                a[j] = temp;
            }
        }
    }
    
    for (int i = 0; i < 10; i++)
    {
        printf("%i",a[i]);
    }